[英]C# async/await await doesn't await
我正在使用.Net 4.7.2 和 C# 7
在 class 中有幾種方法,都是無效的。 其中一些可以異步執行,另一些必須按順序執行。 所以我定義了一個平行列表
private void ExecuteSequential()
{
SomeMethod1();
SomeMethod2();
ExecuteParallelAsync();
SomeMethod3();
SomeMethod4();
}
private async void ExecuteParallelAsync()
{
List<Action> methods = new List<Action>()
{
MyMethod1,
MyMethod2,
MyMethod3
};
await Task.Run( () => { Parallel.ForEach( methods , ( currentMethod ) => currentMethod.Invoke() ); } );
}
在SomeMethod3恰好被執行之前ExecuteParallelAsync應該完全完成。 因為情況並非如此,顯然我對 async 和 await 的使用有問題。
我做錯了什么?
提前致謝!
async void
- 所有賭注都關閉; 實際上,您永遠不應該編寫async void
方法,除非您處於需要它的極少數極端極端情況中,例如異步事件處理程序。
在 SomeMethod3 恰好被執行之前 ExecuteParallelAsync 應該完全完成。
它怎么可能知道? 它是: async void
,這意味着它不會向調用者提供任何指示 state 的內容。
為此,它必須是:
private async Task ExecuteParallelAsync() // or ValueTask
你必須在呼叫現場等待
await ExecuteParallelAsync();
Marc 的答案是正確的解決方案,但我想更多地解釋原因。
了解async
方法的一個非常關鍵的事情是:
await
關鍵字並且await
給定一個不完整的Task
時,該方法會返回- 是的,即使您的方法只完成了一半。 神奇在於它返回的東西。所以讓我們看看你的代碼做了什么:
ExecuteParallelAsync()
。ExecuteParallelAsync()
開始同步運行。Task.Run
返回一個尚未完成的Task
object。await
關鍵字檢查Task
,發現它不完整,所以它創建一個新Task
,注冊該方法的 rest 作為該Task
的延續(在這種情況下沒有什么可做的,但它仍然會發生)並返回。void
它不能返回Task
。 但它仍然返回! 所以這一切意味着ExecuteParallelAsync()
在Task.Run
完成之前返回,而ExecuteSequential()
無法判斷工作何時真正完成。
這通常被稱為“一勞永逸”(即“去做這件事,我不在乎你何時或是否完成”)。
正如 Marc 的回答所指出的那樣,如果您想知道它何時完成,您需要返回一個Task
,然后您可以await
知道它何時完成。
Microsoft 有非常好的關於異步編程的文章,您可能會從閱讀中受益: Asynchronous Programming with async and await
如果您想要一個沒有 async/await 糖關鍵字的實現:
private void ExecuteParallelAsync()
{
List<Action> methods = new List<Action>()
{
MyMethod1,
MyMethod2,
MyMethod3
};
Task.WaitAll(methods.Select((action) => Task.Run(action)).ToArray())
}
public void Method()
{
SomeMethod();
SomeMethod();
ExecuteParallelAsync(); //< ---fire and forget
SomeMethod();
SomeMethod();
Console.ReadLine();
}
private void SomeMethod() => Console.WriteLine($"SomeMethod");
private async void ExecuteParallelAsync()
{
Console.WriteLine($"START");
await Task.Delay(TimeSpan.FromMilliseconds(100));
Console.WriteLine($"END");
}
Output
SomeMethod
SomeMethod
START
SomeMethod
SomeMethod
END
“哦,是的,我忘了等待我的功能”
await ExecuteParallelAsync(); //Change 1. "I forgot to await"
Output
Compilation error (line 12, col 3): The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
Compilation error (line 12, col 3): Cannot await 'void'
解決步驟 2 中的兩個錯誤。
// Change 2
// was:public void Main()
// which caused: "The 'await' operator can only (...)"
public async Task Main()
(...)
// Change 3
// was: private async void ExecuteParallelAsync()
// which caused "Compilation error (line 12, col 3): Cannot await 'void'"
private async Task ExecuteParallelAsync()
跑。
Output:
SomeMethod
SomeMethod
START
END
SomeMethod
SomeMethod
作為參考,我們可以在沒有await
關鍵字的情況下等待ExecuteParallelAsync
,但是await
在所有場景中都是正確的方式,除了少數情況下它不是。
一個完整的小例子:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace test
{
class Program
{
static void Main(string[] args)
{
SomeMethod1();
ExecuteParallelAsync();
SomeMethod2();
Console.ReadKey();
}
static void SomeMethod1()
{
Console.WriteLine("SomeMethod1");
}
static void SomeMethod2()
{
Console.WriteLine("SomeMethod2");
}
static void ExecuteParallelAsync()
{
Console.WriteLine("\tstart of ExecuteParallelAsync");
var rd = new Random();
List<Action> methods = new List<Action>()
{
()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod1"); },
()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod2"); },
()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod3"); },
};
Task.WaitAll(methods.Select((action) => Task.Run(action)).ToArray());
Console.WriteLine("\tend of ExecuteParallelAsync");
}
}
}
結果示例:
SomeMethod1
start of ExecuteParallelAsync
MyMethod2
MyMethod3
MyMethod1
end of ExecuteParallelAsync
SomeMethod2
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.