天天看點

Unity中的多線程程式設計協程與線程的差別線程使用場景Unity多線程程式設計的坑

協程與線程的差別

協程

本質上是單線程程式設計,将一個函數放到多個幀中執行,多個協程無法并發,同一時間,隻有一個協程運作。

  • 優點:
    1. 不需要考慮資料同步的問題
    2. 可以直接通路遊戲對象
    3. 将異步邏輯,以一種類似同步的方法編寫
    4. 性能上沒有較大的開銷
    5. 分散計算壓力,允許将耗時操作分為多步運作
  • 缺點:
    1. 容易産生GC
    2. 無法并發,多線程下載下傳等需求效率無法提升
    3. 部分協程操作可能會阻塞主線程,導緻遊戲卡頓

線程

建立子線程,允許與主線程同時處理邏輯,多個線程支援并發。

    1. 支援并發,可以提高計算效率
    2. 子線程邏輯獨立運作,不會阻塞遊戲主線程
    1. 無法通路遊戲物體
    2. 需要通過加鎖等操作,手動保證資料同步
    3. 線程操作較消耗性能

線程使用場景

  • 操作會造成遊戲卡頓的邏輯
  • 資料處理相關,資料大又不涉及到遊戲物體的功能,如多線程下載下傳、尋路資料計算等

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 });
}