亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

為什么在不將其分配給 Task 變量的情況下直接等待異步函數不起作用

為什么在不將其分配給 Task 變量的情況下直接等待異步函數不起作用

C#
素胚勾勒不出你 2021-11-21 14:32:18
通常,我會執行以下操作public static async Task dosth(){    List<Task> job = new List<Task>();    for (int i = 0; i < 3; i++)    {        job.Add(sleep());    }    Task.WhenAll(job.ToArray());}static async Task sleep(){    await Task.Delay(1000);    Console.WriteLine("Finish new");}它工作順利,沒有問題。但是當我對自己的代碼進行審查時(嘗試使用其他語法來完成相同的工作),我突然發現以下兩個是不同的。public static async Task dosthA(){    //This will be working synchronously, take 3 seconds.    await sleep();    await sleep();    await sleep();    //This will be working asynchronously, take 1 second only.    Task A = sleep();    Task B = sleep();    Task C = sleep();    await A;    await B;    await C;}為什么將異步函數分配給新變量會有所不同?我原本認為它們是一樣的。更新為什么讓我感到困惑的是,實際上在Async-await 的 Microsoft 文檔中,他們在代碼中聲明了以下內容。// Calls to TaskOfTResult_MethodAsync  Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();  int intResult = await returnedTaskTResult;  // or, in a single statement  int intResult = await TaskOfTResult_MethodAsync();  他們實際上是不同的,為什么他們使用//or , in a single statement,僅僅因為它在他們自己的例子中沒有什么不同?
查看完整描述

1 回答

?
函數式編程

TA貢獻1807條經驗 獲得超9個贊

這是因為當您Task在調用Sleep()時返回運行時,即使您正在分配變量。


令人困惑的是,Task如果您在調用之前將其分配給變量(、 或)A,則不會開始B,但事實并非如此。一旦你分配給,就被調用了;因此in方法正在運行。將其分配給變量與否在調用方法時開始;因為在你啟動的方法中。 Cawait A; sleep();Asleep()Tasksleep()TaskTask


知道這一點;你打電話時:


await A;

await B;

await C;

A、B 和 C 已經同時開始……在等待 A 之后,很可能 B 和 C 也已完成或距離完成幾毫秒。


在某些情況下,您可以引用Task尚未啟動的a ,但您必須有意返回非運行Task才能執行此操作。


還要回答對您的問題的編輯。


Tasks 有一個被調用的方法GetAwaiter(),它返回一個TaskAwaiter. 在 C# 中,當您編寫時,您var task = sleep();將實際分配給Task任務變量。同樣,當你編寫await sleep();編譯器時,它會做一些很酷的事情,并且它實際上會調用Task.GetAwaiter()方法;這是訂閱的。在Task將運行并完成時,TaskAwaiter將觸發延續動作。這不能用簡單的答案來解釋,但了解外部邏輯會有所幫助。


除其他外,TaskAwaiter工具ICriticalNotifyCompletion又會執行INotifyCompletion。兩者都有一個方法,OnCompleted(Action)并且UnsafeOnCompleted(Action) (您可以通過命名約定猜測哪個是哪個)。


要注意的另一件事是Task.GetAwaiter()返回 aTaskAwaiter但Task<TResult>.GetAwaiter()返回 a TaskAwaiter<TResult>。兩者沒有太大區別,只是GetResult()兩個任務的方法有區別;這就是在編組回正確的線程上下文時調用的內容。在TaskAwaiter.GetResult()返回void和TaskAwaiter<TResult>.GetResult()回報TResult。


我覺得如果我進一步深入研究,我將不得不寫幾頁來詳細解釋這一切......希望只是解釋你的問題并稍微拉開帷幕就會有足夠的光線來幫助你理解和深入挖掘如果你更好奇。


好的,所以根據下面的評論,我想進一步描述我的答案。


我會從這個簡單的開始;讓我們做一個Task;一個沒有運行的,先看看它。


public Task GetTask()

{

    var task = new Task(() => { /*some work to be done*/ });

    //Now we have a reference to a non-running task.

    return task;

}

我們現在可以調用如下代碼:


public async void DoWork()

{

    await GetTask();

}

……但我們會永遠等待;直到應用程序結束,因為Task從未啟動。然而; 我們可以這樣做:


public async void DoWork()

{

    var task = GetTask();

    task.Start();

    await task;

}

...它將等待運行Task并在Task完成后繼續。


知道了這一點,您可以根據需要進行任意數量的調用,GetTask()并且只會引用Task尚未啟動的 s。


在您的代碼中正好相反,這很好,因為這是最常用的方式。我鼓勵您確保您的方法名稱通知用戶您如何返回Task. 如果Task已經在運行,最常見的約定是方法名稱以 結尾Async。Task為了清楚起見,這是另一個通過跑步來做的例子。


public Task DoTaskAsync()

{

    var task = Task.Run(() => { /*some work to be done*/ });

    //Now we have a reference to a task that's already running.

    return task;

}

現在我們很可能會像這樣調用這個方法:


public async void DoWork()

{

    await DoTaskAsync();

}

然而; 請注意,如果我們只是想像Task我們之前所做的那樣引用,我們可以,唯一的區別是這Task是在先前沒有的地方運行。所以這個代碼是有效的。


public async void DoWork()

{

    var task = DoTaskAsync();

    await task;

}

最大的收獲是 C# 如何處理 async / await 關鍵字。 async告訴編譯器該方法將成為Task. 簡而言之; 編譯器知道查找所有await調用并將方法的其余部分放在延續中。


的await關鍵字告訴編譯器調用Task.GetAwaiter()上的方法Task(和基本上訂閱INotifyCompletion和ICriticalNotifyCompletion)轉換成信號的方法中的延續。


我想補充一點,以防萬一你不知道。如果您確實有多個任務要等待,但寧愿等待一項任務,就好像它們都是一項任務一樣,那么您可以使用Task.WhenAll()So 來代替:


var taskA = DoTaskAsync();

var taskB = DoTaskAsync();

var taskC = DoTaskAsync();


await taskA;

await taskB;

await taskC;

你可以像這樣寫得更干凈一點:


var taskA = DoTaskAsync(); 

var taskB = DoTaskAsync(); 

var taskC = DoTaskAsync();


await Task.WhenAll(taskA, taskB, taskC);

并且內置了更多的方法來做這種事情;只是探索它。


查看完整回答
反對 回復 2021-11-21
  • 1 回答
  • 0 關注
  • 199 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號