什麼是協程
說到協程,我們先了解什麼是非同步,非同步簡單說來就是,我要發起一個呼叫,但是這個被呼叫方(可能是其它執行緒,也可能是IO)出結果需要一段時間,我不想讓這個呼叫阻塞住呼叫方的整個執行緒,因此傳給被呼叫方一個回撥函式,被呼叫方執行完成後回撥這個回撥函式就能通知呼叫方繼續往下執行。舉個例子:
下面的程式碼,主執行緒一直迴圈,每迴圈一次sleep 1毫秒,計數加一,每10000次列印一次。
private static void Main() { int loopCount = 0; while (true) { int temp = watcherValue; Thread.Sleep(1); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } }
這時我需要加個功能,在程式一開始,我希望在5秒鐘之後列印出loopCount的值。看到5秒後我們可以想到Sleep方法,它會阻塞執行緒一定時間然後繼續執行。我們顯然不能在主執行緒中Sleep,因為會破壞掉每10000次計數列印一次的邏輯。
// example2_1 class Program { private static int loopCount = 0; private static void Main() { OneThreadSynchronizationContext _ = OneThreadSynchronizationContext.Instance; WaitTimeAsync(5000, WaitTimeFinishCallback); while (true) { OneThreadSynchronizationContext.Instance.Update(); Thread.Sleep(1); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } } private static void WaitTimeAsync(int waitTime, Action action) { Thread thread = new Thread(()=>WaitTime(waitTime, action)); thread.Start(); } private static void WaitTimeFinishCallback() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); } /// <summary> /// 在另外的執行緒等待 /// </summary> private static void WaitTime(int waitTime, Action action) { Thread.Sleep(waitTime); // 將action扔回主執行緒執行 OneThreadSynchronizationContext.Instance.Post((o)=>action(), null); } }
我們在這裡設計了一個WaitTimeAsync方法,WaitTimeAsync其實就是一個典型的非同步方法,它從主執行緒發起呼叫,傳入了一個WaitTimeFinishCallback回撥方法做引數,開啟了一個執行緒,執行緒Sleep一定時間後,將傳過來的回撥扔回到主執行緒執行。OneThreadSynchronizationContext是一個跨執行緒佇列,任何執行緒可以往裡面扔委託,OneThreadSynchronizationContext的Update方法在主執行緒中呼叫,會將這些委託取出來放到主執行緒執行。為什麼回撥方法需要扔回到主執行緒執行呢?因為回撥方法中讀取了loopCount,loopCount在主執行緒中也有讀寫,所以要麼加鎖,要麼永遠保證只在主執行緒中讀寫。加鎖是個不好的做法,程式碼中到處是鎖會導致閱讀跟維護困難,很容易產生多執行緒bug。這種將邏輯打包成委託然後扔回另外一個執行緒是多執行緒開發中常用的技巧。
我們可能又有個新需求,WaitTimeFinishCallback執行完成之後,再想等3秒,再列印一下loopCount。
private static void WaitTimeAsync(int waitTime, Action action) { Thread thread = new Thread(()=>WaitTime(waitTime, action)); thread.Start(); } private static void WaitTimeFinishCallback() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); WaitTimeAsync(3000, WaitTimeFinishCallback2); } private static void WaitTimeFinishCallback2() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); }
我們這時還可能改需求,需要在程式啟動5秒後,接下來4秒,再接下來3秒,列印loopCount,也就是上面的邏輯中間再插入一個3秒等待。
private static void WaitTimeAsync(int waitTime, Action action) { Thread thread = new Thread(()=>WaitTime(waitTime, action)); thread.Start(); } private static void WaitTimeFinishCallback() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); WaitTimeAsync(4000, WaitTimeFinishCallback3); } private static void WaitTimeFinishCallback3() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); WaitTimeAsync(3000, WaitTimeFinishCallback2); } private static void WaitTimeFinishCallback2() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); }
這樣中間插入一段程式碼,顯得非常麻煩。這裡可以回答什麼是協程了,其實這一串串回撥就是協程。
ET開源地址地址:egametang/ET: Unity3D Client And C# Server Framework (github.com) qq群:474643097