Task.Run Vs Task.Factory.StartNew


.Net 4中,Task.Factory.StartNew是啟動一個新Task的首選方法。它有很多重載方法,使它在具體使用當中可以非常靈活,通過設置可選參數,可以傳遞任意狀態,取消任務繼續執行,甚至控制任務的調度行為。所有這些能力也帶來了復雜性的提升,你必須知道何時應該使用何種重載方法,提供哪種調度方式等等。並且Task.Factory.StartNew這種寫法也不夠簡潔明快,至少對它使用的主要場景不夠快,一般它使用的主要場景只是將一個工作任務丟給一個后台線程執行而已。

於是,在.NET Framework 4.5開發者預覽版中,微軟引進了新的Task.Run方法。新方法不是為了替代舊的Task.Factory.StartNew方法,只是提供了一種使用Task.Factory.StartNew方法的更簡潔的形式,而不需要去指定那一系列參數。這是一個捷徑,事實上,Task.Run的內部實現邏輯跟Task.Factory.StartNew一樣,只是傳遞了一些默認參數。比如當你使用Task.Run

Task.Run(someAction);

實際上等價於:

Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

使用這些默認參數,Task.Run就能用於大多數情況——只是將任務簡單的交給后台線程池去執行(這也是使用TaskScheduler.Default參數的目標)。這也並不意味着Task.Factory.StartNew方法就不必再使用了,它仍然有很多重要的用處。你可以通過控制TaskCreationOptions參數來控制任務的行為,你也可以通過控制TaskScheduler來控制任務應該如何排隊如何運行,你也可以使用重載方法中的接受對象狀態那個參數,對於一些性能敏感的代碼,它可以用於避免閉包以及相應的資源分配。不過對於上面那個簡單的例子,Task.Run是最友好。

Task.Run提供了八種重載方式,用於提供下面這幾種組合情況:

  1. 普通任務(Task)和帶返回值任務(Task<TResult>
  2. 可取消任務(Cancelable)和不可取消任務(non-cancelabl
  3. 同步委托(Synchronous)和異步委托(Asynchronous

前兩個很明顯,對於第一點如果是用的Task做返回值的重載方法,那么該任務就沒有返回值,如果是用的Task<TResult>做返回值的重載方法,那么該任務就有一個類型為TResult的返回值。對於第二點,也有接受CancellationToken參數的重載,可以在任務開始之前執行取消操作,然后並行任務(Task Parallel Library——TPL)就可以自然的過度到取消狀態。
第三點要更有趣一些,它直接關系到Visual studio 11中的C#和Visual Basic的異步語言支持。我們先使用Task.Factory.StartNew來展示下這個問題,如果有下面一段代碼:

var t = Task.Factory.StartNew(() => 
{ 
    Task inner = Task.Factory.StartNew(() => {}); 
    return inner; 
});

這里t的類型會被推斷為Task<Task>,因為此處任務的委托類型是Func<TResult>,所以這里TResult的類型就是Task,於是StartNew方法就返回Task<Task>,類似的,我可以改變成下面這種寫法:

var t = Task.Factory.StartNew(() => 
{ 
    Task<int> inner = Task.Factory.StartNew(() => 42)); 
    return inner; 
});

此處的t的類型自然是Task<Task<int>>,任務的委托類型還是Func<TResult>TResult的類型就是Task<int>StartNew方法就返回Task<Task<int>>。這有什么關系呢?考慮下如果我們現在使用下面這種寫法:

var t = Task.Factory.StartNew(async delegate 
{ 
    await Task.Delay(1000); 
    return 42; 
});

這里使用了async關鍵詞,編譯器會將這個委托映射成Func<Task<int>>,調用這個委托最終會返回Task<int>。因為這個這個委托是Func<Task<int>>TResult的類型就是Task<int>,所以最后t的類型應該是Task<Task<int>>,而不是Task<int>

為了應對這幾種情況,在.Net 4中引入了Unwrap方法。Unwrap方法有兩種重載形式,均是擴展方法的形式,一種是針對類型Task<Task>,另一種是針對<Task<TResult>>。微軟只所以要把這個方法命名為解包(Unwrap),是因為這個方法可以返回任務的實際結果。對Task<Task>調用Unwrap方法可以返回一個新的Task(就像內部任務的一個代理一樣)代表它的內部任務。相似的,對Task<Task<TResult>>調用Unwrap返回一個新的Task<TResult>代表它的內部任務。但是,如果外部任務失敗了或者取消了,就不會有內部任務了,因為沒有任務運行完成,所以代理任務也就變成了外部任務的狀態。回到前面的例子,如果想讓t代表內部任務的返回值(在這個例子中,這個值是42),那么應該像下面這樣寫:

var t = Task.Factory.StartNew(async delegate 
{ 
    await Task.Delay(1000); 
    return 42; 
}).Unwrap();

現在,變量t的類型是Task<int>,代表異步調用的結果。

現在回到Task.Run,因為微軟想讓開發者盡可能的使用這個方法來啟用后台任務,並且可以配合async/await使用,所以微軟決定在Task.Run方法中內建unwrapping的功能。這也是上面第三點所指的內容,Task.Run的重載方法中有可以接受Action(沒有返回值的任務)的,有接受Func<TResult>(返回TResult的任務)的,有接受Func<Task>(返回一個異步任務的任務)的,還有接受Func<Task<TResult>>(返回一個帶TResult類型返回值的異步任務的任務)的。總的來說,Task.Run方法提供了上面Task.Factory.StartNew方法相同的unwrapping操作。於是,我們可以這樣寫:

var t = Task.Run(async delegate 
{ 
    await Task.Delay(1000); 
    return 42; 
});

t的類型是Task<int>,此處Task.Run執行的重載方法等價於:

var t = Task.Factory.StartNew(async delegate 
{ 
    await Task.Delay(1000);  
    return 42; 
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();

如前所述,這是一個快捷方式。

上面講的全部類容都意味着你可以使用Task.Run調用標准的lambdas/anonymous方法或是異步lambdas/anonymous方法,最后總會按你所期望的行為運行。如果我們想讓任務在后台運行並且想等待它的結果,那么可以像下面這樣寫:

int result = await Task.Run(async () => 
{ 
    await Task.Delay(1000); 
    return 42; 
});

此處變量result的類型正是你所期望的int,並且在該任務被調用大約1秒鍾后,變量result的值被設置為42。

有趣的是,新的await關鍵字被認為是等價於Unwrap方法的一種新語法形式。於是,如果我們回到上面那個Task.Factory.StartNew例子,我們可以先用Unwrap重寫上面那個代碼片段:

int result = await Task.Factory.StartNew(async delegate 
{ 
    await Task.Delay(1000); 
    return 42; 
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();

或者,可以使用第二個await替換Unwrap

int result = await await Task.Factory.StartNew(async delegate 
{ 
    await Task.Delay(1000); 
    return 42; 
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

這里的await await雖然看着別扭,但是並沒有問題。Task.Factory.StartNew方法返回一個Task<Task<int>>,對Task<Task<int>>使用await實際上返回Task<int>,然后再對Task<int>使用await最后返回int,難道不是這樣嗎?

原文鏈接,2011年10月24日發布:
http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM