我正在学习如何在控制台应用程序中使用异步函数,但无法使Task.WhenAll等到所有任务完成。以下代码有什么问题?它同步工作。提前谢谢。
static void Main(string[] args)
{
...
IncluiValores(...);
...
}
static async void IncluiValores(...)
{
Task<List<int>> res1 = att.GetAIDBAPI(att);
Task<List<int>> res2 = att.GetAIDBAPI(att2);
List<int>[] res = await Task.WhenAll(res1, res2);
...
}
更新 - 功能定义:
public async Task<List<int>> GetAIDBAPI(Attributes attributes)
{
List<int> results = null;
Connections client0 = new Connections();
HttpClient client = client0.OpenAPIConnection(attributes.User[0], attributes.Pwd, attributes.Server, attributes.Chave, attributes.Server2);
HttpResponseMessage response = await client.PostAsJsonAsync("api/Attributes/ID/Bulk", attributes);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
results = JsonConvert.DeserializeObject<dynamic>(content).ToObject<List<int>>();
}
else
{
var content = "[{-1}]";
var result = JsonConvert.DeserializeObject<dynamic>(content);
results = result.ToObject<List<int>>();
}
return results;
}
更新2 - 单独的上下文
static void Main(string[] args)
{
AsyncContext.Run(() => MainAsync(args));
}
static async void MainAsync(string[] args)
{
await IncluiValores(...);
}
static async Task IncluiValores(...)
{
Task<List<int>> res1 = att.GetAIDBAPI(att);
Task<List<int>> res2 = att.GetAIDBAPI(att2);
List<int>[] res = await Task.WhenAll(res1, res2); // <- Error here
//Collection was modified; enumeration operation may not execute
...
}
//Tried to change to code below but it does not wait.
static async Task IncluiValores(...)
{
Task<List<int>> res1 = att.GetAIDBAPI(att);
Task<List<int>> res2 = att.GetAIDBAPI(att2);
await Task.WhenAll(res1, res2); // <- No error, just doesn't wait.
list.Add(res1.Result[0]);
}
答案 0 :(得分:6)
您正在调用async void
方法,这本身就意味着您无法await
结果。每当省略await
时,就会破坏同步链。该操作实际上是异步发生的,而不是通过await
“重新同步”。控件返回给调用者,而(将来的某个时间)操作将异步恢复。
请注意,await
是return
。只有await
的一致使用才能为您提供同步。停止使用async void
- 将其更改为async Task
,并确保正确await
结果。您的MainAsync
方法也是如此。 Task
是异步方法的void
。
只有一种情况,你应该看到async void
,这是在遗留框架的事件处理程序的同步上下文中(例如在Winforms中)。如果async
方法有可能返回Task
,那么它确实应该。不要破坏链条。
答案 1 :(得分:3)
错误是您的主要功能不等待完成程序IncluiValores。您的主程序在程序IncluiValores完成之前完成。
由于此错误,我认为您在使用async-await时仍然无法理解会发生什么。
StackOverflow上有人(唉,我再也找不到了),用以下的比喻向我解释。
附加:我找到了隐喻
It is in this interview with Eric Lippert
在中间某处搜索async-await
结束辩护
假设你需要做早餐。你想要烤一些面包,煮一些鸡蛋和泡茶。
<强>同步强>
你看到你做了很多等待,这是浪费时间,更不用说你的面包在茶完成时可能很冷。
如果你不是一直等待,但会同时开始,那会更有效率
使用async-await:使用一个线程进行异步
开始为你的茶开水,但不要等待服务员煮沸。记住当茶壶煮沸时你应该做什么(记住这是任务C)
等到任务A / B / C完成任务。继续你记得任务完成后你应该做什么。如果这需要一些其他等待(鸡蛋或茶准备好的时间),不要等待它,但记住它作为任务D和E并开始等待所有未完成的任务。
请注意,在这种方法中,仍然只有一个人在做所有的事情。如果您使用async-await,则只涉及一个线程。这个线程只是等待它真的无关。这样做的好处是,您不会遇到使用多个线程时通常遇到的问题。
使用多个线程异步
你可以雇佣几个厨师:一个是烤面包,一个是煮鸡蛋。这是一种昂贵的方法:启动多个线程,而线程什么都不做,只是等待大部分时间。你也有三个厨师必须同步的问题,以确保他们不会同时使用单火炉。
Stephen Cleary 撰写了一篇全面的文章,描述了Async and Await中的async-await行为(谢谢Stephen!)
static void Main(string[] args)
{
var breakFast = await Task.Run( () => MakeBreakFast());
// once here I know breakfast is ready
Eat(breakFast);
}
private static async Task<BreakFast> MakeBreakFast()
{
var taskToastBread = ToastBreadAsync();
// do not await. As soon as the procedure awaits come back to do the next statement:
var taskBoilEggs = BoilEggsAsync();
// again do not await. Come back as the procedure awaits
var taskMakeTea = MakeTeaAsync();
// do not wait, but come bask as soon as the procedure await
// now wait until all three tasks are finished:
await Task.WhenAll (new Task[] {taskToasBread, taskBoilEggs, taskMakeTea});
// if here: all tasks are finished. Property Result contains the return value of the Task:
return new BreakFast()
{
Toast = taskToastBread.Result,
Eggs = taskBoilEggs.Result,
Tea = taksMakeTea.Result,
}
}
private static Task<Toast> ToastBreadAsync()
{
var sliceOfBread = Loaf.CutSliceOfBread();
Toaster.Insert(sliceOfBread);
await Toaster.Toast();
// the function does not wait but return to the caller.
// the next is done when the caller await and the toaster is ready toasting
var toast = Toaster.Remove();
return Toast();
}
private static Task<Eggs> BoilEggsAsync()
{
var eggPan = ...
await eggPan.BoilWater();
var eggs = Fridge.ExtreactEggs();
EggPan.Insert(eggs);
await Task.Delay(TimeSpan.FromMinutes(7));
return EggPan.Remove();
}
你现在可能已经知道如何泡茶了。