說到協程,我們先了解什麼是異步,異步簡單說來就是,我要發起一個調用,但是這個被調用方(可能是其它線程,也可能是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}"); }
這樣中間插入一段代碼,顯得非常麻煩。這裏可以回答什麼是協程了,其實這一串串回調就是協程。