1. 程式人生 > >15.5.6 【Task實現細節】跟蹤棧

15.5.6 【Task實現細節】跟蹤棧

  談到棧幀(stack frame)時,可能會想到在方法中宣告的區域性變數。當然,可能還會注意到 一些隱藏的區域性變數,如 foreach 迴圈中的迭代器。但棧上的內容不止這些,至少邏輯上是這樣  。 很多情況下,在一些表示式還沒有計算出來前,另一些中間表示式是不能使用的。最簡單的例子 莫過於加法等二進位制操作和方法呼叫了。 舉個極簡單的例子,思考下面這一行:

  var x = y * z;

在基於棧的虛擬碼中,將為如下形式:

  push y
  push z
  multiply
  store

現在假設有如下 await 表示式:

  var x = y * await z;

  在等待 z 之前,需計算 y 並將其儲存至某處,但可能會從 MoveNext() 方法立即返回,因此需 要一個邏輯棧來儲存 y 。在執行後續操作時,可以重新儲存該值,然後執行乘法。在這種情況下, 編譯器可將 y 的值賦值給 stack 例項變數。這會引起裝箱,但同時也意味著可以使用單個變數。 這是個簡單的例子。假設有多個值需要儲存,如下所示:

  Console.WriteLine("{0} :{1}", x, await task);

在邏輯棧上需要儲存格式化的字串和 x 值。此時編譯器會建立一個包含兩個值的Tuple<string, int> ,並將其儲存在 stack 的引用上。和 awaiter 一樣,同一時間只需要一個邏輯棧,因此一直使用相同的變數是沒有問題的。在後續操作中,可以從元組(tuple)中獲取
  實參,並用於方法呼叫。可下載的原始碼中包含了完整的反編譯示例,其中包括以上兩條語句( LogicalStack.cs 和 LogicalStackDecompiled.cs )。

  第二條語句最終將使用以下程式碼:

 1                     string localArg0 = "{0} {1}";
 2                     int localArg1 = x;
 3                     localAwaiter = task.GetAwaiter();
 4                     if (localAwaiter.IsCompleted)
 5                     {
 6                         goto SecondAwaitCompletion;
7 } 8 var localTuple = new Tuple<string, int>(localArg0, localArg1); 9 stack = localTuple; 10 state = 1; 11 awaiter = localAwaiter; 12 builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); 13 doFinallyBodies = false; 14 return; 15 SecondAwaitContinuation: 16 localTuple = (Tuple<string, int>) stack; 17 localArg0 = localTuple.Item1; 18 localArg1 = localTuple.Item2; 19 stack = null; 20 localAwaiter = awaiter; 21 awaiter = default(TaskAwaiter<int>); 22 state = -1; 23 SecondAwaitCompletion: 24 int localArg2 = localAwaiter.GetResult(); 25 Console.WriteLine(localArg0, localArg1, localArg2);

此處加粗顯示的是與邏輯棧元素相關的程式碼。 目前所有需瞭解的內容均已介紹完畢。如果你跟上了我們的步伐,就肯定比99%的開發者更 為了解背後的細節。第一次沒能完全理解也沒有關係。在閱讀這些狀態機程式碼時,如果感到無法 理清頭緒,可以暫時放鬆一下,過一會兒再來繼續學習。