簡體   English   中英

Task.Run() 中的異步/等待操作

[英]Async/Await action within Task.Run()

Task.Run(()=>{})將動作委托放入隊列並返回 task 。 Task.Run()中使用 async/await 有什么好處嗎? 我知道Task.Run()是必需的,因為如果我們想直接使用 await,那么調用方法將需要設為異步並會影響調用位置。

這是在Task.Run()具有異步等待的示例代碼。 此處提供了完整示例: 創建預先計算的任務

Task.Run(async () => { await new WebClient().DownloadStringTaskAsync("");});

或者,這可以完成:

Task.Run(() => new WebClient().DownloadStringTaskAsync("").Result;);

由於Task.Run()Task.Run()都會將工作排隊並由線程池選擇, Task.Run()的 async/await 是否有點多余?

在 Task.Run() 中使用 async/await 有什么好處嗎?

是的。 Task.Run在線程池線程上運行一些操作。 如果這樣的動作做了一些 IO 工作並通過await異步等待 IO 操作完成,那么這個線程池線程可以在 IO 操作仍在運行時被系統用於其他工作。

例子:

Task.Run( async () =>
{
    DoSomeCPUIntensiveWork();

    // While asynchronously waiting for this to complete, 
    // the thread is given back to the thread-pool
    var io_result = await DoSomeIOOperation(); 

    DoSomeOtherCPUIntensiveWork(io_result);
});

在 Task.Run() 中使用 async/await 有什么好處嗎

您也可以問相反的問題:為什么要在 Task.Run 中包裝 async/await 代碼?!

一旦第一個等待被命中(對未完成的任務進行操作),異步方法就會返回給調用者。 因此,如果異步方法的第一次執行“連續”需要很長時間, Task.Run將改變行為:它將導致該方法立即返回並在線程池上執行第一個“連續”。

這在 UI 方案中很有用,因為這樣您可以 100% 確保您沒有阻止 UI。 示例:即使您使用其中一種異步方法(這基本上是庫錯誤/設計錯誤), HttpWebRequest也會同步進行 DNS 解析。 這可以暫停 UI 線程。 因此,您可以使用 Task.Run 來 100% 確保 UI 不會被阻塞超過幾微秒。

所以回到最初的問題:為什么在 Task.Run 主體內等待? 出於與您通常等待相同的原因:解除線程阻塞。

在您鏈接的示例中,主線程被阻塞,直到異步操作完成。 它被調用Wait()阻止Wait()順便說一句,這通常是一個壞主意)。

讓我們看看鏈接示例中DownloadStringAsync的返回:

return Task.Run(async () =>
{
    content = await new WebClient().DownloadStringTaskAsync(address);
    cachedDownloads.TryAdd(address, content);
    return content;
});

為什么要將其包裝在Task 考慮一下您的選擇。 如果您不想將其包裝在Task ,您將如何確保該方法返回Task<string>並且仍然有效? 您當然async方法標記為async 但是,如果您將方法標記為async並對其調用Wait ,則很可能會以死鎖告終,因為主線程正在等待工作完成,而您阻塞了主線程,因此它不能讓你知道它已經完成。

將方法標記為async ,狀態機將在調用線程上運行,但是在您的示例中,狀態機在單獨的線程上運行,這意味着在主線程上幾乎沒有工作。

從非異步方法調用異步

當我們嘗試在非異步方法中調用異步方法時,我們會做一些類似的事情。 特別是如果異步方法是已知數量。 盡管我們使用了更多的 TaskFactory ......適合一種模式,使其更容易調試,確保每個人都采用相同的方法(並且 -- 如果 async-->sync 開始出現問題,我們就會窒息)。

所以,你的例子

想象一下,在您的示例中,您有一個非異步函數。 並且,在該函數中,您需要調用await webClient.DoSomethingAsync() 您不能在非異步函數內調用 await - 編譯器不會讓您這樣做。

選項 1:僵屍感染

您的第一個選擇是一路爬行備份您的調用堆棧,沿途將每個 da*n 方法標記為異步並在各處添加等待。 到處。 就像,無處不在。 然后——由於所有這些方法現在都是異步的,您需要使引用它們的方法全部異步。

順便說一句,這可能是許多 SO 愛好者將要提倡的方法。 因為,“阻塞線程是個壞主意。”

所以。 是的。 這種“讓異步成為異步”的方法意味着您用來獲取 json 對象的小庫例程剛剛觸及並觸及了 80% 的代碼。 誰會打電話給 CTO 讓他知道?

選項 2:只用一個僵屍

或者,你可以將你的異步封裝在一些像你這樣的函數中......

return Task.Run(async () => {
     content = await new WebClient().DoSomethingAsync();
     cachedDownloads.TryAdd(address, content);
     return content;
});

Presto...僵屍感染已包含在一段代碼中。 我將把它留給位機制來爭論如何/為什么在 CPU 級別執行。 我真的不在乎。 我關心的是沒有人需要向 CTO 解釋為什么整個庫現在應該是 100% 異步的(或類似的東西)。

確認,用Task.Run包裝await產生 2 個線程而不是一個。

Task.Run(async () => { //thread 1
 await new WebClient().DownloadStringTaskAsync(""); //thread 2
});

假設您有兩個這樣包裝的調用,它將產生 2 x 2 = 4 個線程。

最好用簡單的await來調用它們。 例如:

Task<byte[]> t1 = new WebClient().DownloadStringTaskAsync("");
Task<byte[]> t2 = new WebClient().DownloadStringTaskAsync("");
byte[] t1Result = await t1;
byte[] t2Result = await t2;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM