協程與線程的差別
協程
本質上是單線程程式設計,将一個函數放到多個幀中執行,多個協程無法并發,同一時間,隻有一個協程運作。
- 優點:
- 不需要考慮資料同步的問題
- 可以直接通路遊戲對象
- 将異步邏輯,以一種類似同步的方法編寫
- 性能上沒有較大的開銷
- 分散計算壓力,允許将耗時操作分為多步運作
- 缺點:
- 容易産生GC
- 無法并發,多線程下載下傳等需求效率無法提升
- 部分協程操作可能會阻塞主線程,導緻遊戲卡頓
線程
建立子線程,允許與主線程同時處理邏輯,多個線程支援并發。
-
- 支援并發,可以提高計算效率
- 子線程邏輯獨立運作,不會阻塞遊戲主線程
-
- 無法通路遊戲物體
- 需要通過加鎖等操作,手動保證資料同步
- 線程操作較消耗性能
線程使用場景
- 操作會造成遊戲卡頓的邏輯
- 資料處理相關,資料大又不涉及到遊戲物體的功能,如多線程下載下傳、尋路資料計算等
Unity多線程程式設計的坑
Unity多線程程式設計有許多坑,這也是官方建議使用協程的原因,這裡列舉了部分坑及其解決方案
編譯器環境下停止遊戲後分線程仍在運作
描述
編譯器環境下停止遊戲是不會銷毀主線程,這也意味着遊戲過程中開啟的子線程,也不會遊戲的停止而銷毀,雖然這個問題僅僅會在開發階段出現,但是也很容易出現許多不可預知的BUG,浪費時間去修複。
解決方案
注意在OnApplicationQuit、OnDestroy等生命周期内,加入子線程的銷毀,保證停止遊戲後,會手動銷毀線程。
HTTP多線程開發時,出現“連接配接被異常關閉”的異常
C#中Http請求的并發連接配接數預設最大為2,這也意味着,多線程中,超過兩個線程并發發送HTTP請求,就會出現錯誤
可以通過System.Net.ServicePointManager.DefaultConnectionLimit來設定最高并發數,建議不要超過1024
System.Net.ServicePointManager.DefaultConnectionLimit = 512;
子線程通路遊戲物體,出現異常
多線程程式設計時,子線程回調需要通路遊戲物體,但是Unity隻有主線程允許通路遊戲物體
SynchronizationContext.Current代表主線程
子線程可通過SynchronizationContext.Current.Post(SendOrPostCallback d, object state)向主線程通信,讓主線程執行具體的邏輯,下面封裝了幾個快速通信至主線程回調的函數,可以直接使用
/// <summary>
/// 主線程
/// </summary>
private SynchronizationContext _mainThreadSynContext;
...
_mainThreadSynContext = SynchronizationContext.Current; //需要在主線程内指派
...
/// <summary>
/// 通知主線程回調
/// </summary>
private void PostMainThreadAction(Action action)
{
_mainThreadSynContext.Post(new SendOrPostCallback((o) =>
{
Action e = (Action)o.GetType().GetProperty("action").GetValue(o);
if (e != null) e();
}), new { action = action });
}
private void PostMainThreadAction<T>(Action<T> action, T arg1)
{
_mainThreadSynContext.Post(new SendOrPostCallback((o) =>
{
Action<T> e = (Action<T>)o.GetType().GetProperty("action").GetValue(o);
T t1 = (T)o.GetType().GetProperty("arg1").GetValue(o);
if (e != null) e(t1);
}), new { action = action, arg1 = arg1 });
}
public void PostMainThreadAction<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2)
{
_mainThreadSynContext.Post(new SendOrPostCallback((o) =>
{
Action<T1, T2> e = (Action<T1, T2>)o.GetType().GetProperty("action").GetValue(o);
T1 t1 = (T1)o.GetType().GetProperty("arg1").GetValue(o);
T2 t2 = (T2)o.GetType().GetProperty("arg2").GetValue(o);
if (e != null) e(t1, t2);
}), new { action = action, arg1 = arg1, arg2 = arg2 });
}