2015-02-06 26 views
3

考慮下面的C#代碼

var L1 = 
Task.Run(() => 
{ 
    return Task.Run(() => 
    { 
     return Task.Run(() => 
     { 
      return Task.Run(() => 
      { 
       return new Dummy(); 
      }); 
     }); 
    }); 
}); 

var L2 = 
Task.Run(async() => 
{ 
    return await Task.Run(async() => 
    { 
     return await Task.Run(async() => 
     { 
      return await Task.Run(async() => 
      { 
       return new Dummy(); 
      }); 
     }); 
    }); 
}); 

var L3 = 
Task.Run(async() => 
{ 
    return Task.Run(async() => 
    { 
     return Task.Run(async() => 
     { 
      return Task.Run(async() => 
      { 
       return new Dummy(); 
      }); 
     }); 
    }); 
}); 


var r1 = L1.Result; 
var r2 = L2.Result; 
var r3 = L3.Result; 


===================== =================================================
通過乍一看,L1,L2和L3都長得像
Task<Task<Task<Task<Dummy>>>>
原來,L1和L2是簡單Task<Dummy>
所以,我擡頭一看MSDN的Task.Run
(我的重載Task.Run是:Task.Run<TResult>(Func<Task<TResult>>)
MSDN說:怪異編譯器行爲等待關鍵字

返回值是:任務(TResult)表示由函數返回的任務(TResult)的代理。

它還說,在備註:

Run<TResult>(Func<Task<TResult>>)方法是通過語言使用 編譯器支持異步和等待關鍵字。它並不打算從用戶代碼直接調用 。


所以,它看起來像我不應該在我的代碼中使用此重載Task.Run,​​
,如果我這樣做,編譯器將剝離的Task<Task<Task<...<TResult>>>>額外的層,只是給你一個Task<TResult>
如果您將鼠標放在L1和L2,它會告訴你它是Task<Dummy>,不是我
預期Task<Task<Task<Task<Dummy>>>>

到目前爲止好,直到我看L3
L3看起來幾乎完全一樣,L1和L2。所不同的是:
L3只有async關鍵字,而不是await,不同於L1和L2,其中L1具有它們都不和L2具有兩者

令人驚訝地,L3現在被認爲是作爲Task<Task<Task<Task<Dummy>>>>,不同於L1和L2 ,其中兩個被認爲Task<Dummy>

我的問題:
1.
是什麼原因導致的編譯器從L1和L2區別對待L3。爲什麼簡單地增加'async'到L1(或L2移除await)使編譯器以不同的方式對待它?


2.
如果您將更多Task.Run級聯到L1/L2/L3,Visual Studio將崩潰。我使用VS2013,如果我級聯了5層或更多層的Task.Run,​​它會崩潰。 4層是最好的我可以得到,這就是爲什麼我用4層作爲我的例子。只有我嗎 ? 編譯器在翻譯Task.Run時會發生什麼情況?

由於

+0

我得到了7個關卡,然後VS2013變得沒有反應。這可能是我的擴展之一(特別是Resharper),這是應該指責的;我沒有去檢查那個。 – 2015-02-06 02:27:08

回答

6

什麼導致編譯器從L1和L2區別對待L3。爲什麼簡單地向L1添加「異步」(或者從L2中移除等待)會導致編譯器以不同的方式對待它?

因爲asyncawait更改了lambda表達式中的類型。您可以將async視爲「添加」一個Task<>包裝,並將await視爲「刪除」一個Task<>包裝。

只要考慮最內層調用中涉及的類型。第一,L1:

return Task.Run(() => 
{ 
    return new Dummy(); 
}); 

類型的() => { return new Dummy(); }Func<Dummy>,等等的that overload of Task.Run返回類型爲Task<Dummy>

所以() => ###Task<Dummy>###的類型是Func<Task<Dummy>>,它調用different overload of Task.Run,返回類型爲Task<Dummy>。等等。

現在考慮L2:

return await Task.Run(async() => 
{ 
    return new Dummy(); 
}); 

類型的async() => { return new Dummy(); }Func<Task<Dummy>>,所以that overload of Task.Run返回類型爲Task<Dummy>

async() => await ###Task<Dummy>###的類型是Func<Task<Dummy>>,所以它調用same overload of Task.Run,結果類型爲Task<Dummy>。等等。

最後,L3:

return Task.Run(async() => 
{ 
    return new Dummy(); 
}); 

類型的async() => { return new Dummy(); }再次是Func<Task<Dummy>>,所以that overload of Task.Run返回類型爲Task<Dummy>

類型的async() => { return ###Task<Dummy>### }Func<Task<Task<Dummy>>>。請注意嵌套的任務。所以,same overload of Task.Run被再次調用,但這次返回類型爲Task<Task<Dummy>>

現在,您只需重複每個級別。 async() => { return ###Task<Task<Dummy>>### }的類型是Func<Task<Task<Task<Dummy>>>>same overload of Task.Run被再次調用,但這次返回類型爲Task<Task<Task<Dummy>>>。等等。

如果您將更多Task.Run級聯到L1/L2/L3,Visual Studio將崩潰。我使用VS2013,如果我級聯了5層或更多層的Task.Run,​​它會崩潰。 4層是最好的我可以得到,這就是爲什麼我用4層作爲我的例子。只有我嗎 ?編譯器在翻譯Task.Run時發生了什麼?

誰在乎呢?沒有現實世界的代碼會做到這一點。有一些衆所周知的情況對於編譯器在合理的時間範圍內處理起來非常困難; using lambda expressions in method overload resolution is one。使用lambda表達式嵌套調用makes the compiler work exponentially harder

+1

真棒答案! – 2015-02-06 02:43:36

+0

真棒答案x 2!我現在沒有更多的問題了,謝謝 – 2015-02-06 02:59:31