简体   繁体   English

任务。什么时候不等

[英]Task.WhenAll not waiting

I am learning how to use async functions in console application but can't make the Task.WhenAll wait until all tasks are completed. 我正在学习如何在控制台应用程序中使用异步函数,但无法使Task.WhenAll等到所有任务完成。 What is wrong with the following code? 以下代码有什么问题? It works synchronously. 它同步工作。 Thank you in advance. 先感谢您。

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);

    ...
}

UPDATE - Function Definition: 更新 - 功能定义:

    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;

    }

UPDATE 2 - Separate Context 更新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]);
}

You're calling an async void method, which inherently means you have no way of await ing the result. 您正在调用async void方法,这本身就意味着您无法await结果。 Whenever you omit await , you're breaking the synchronization chain. 无论何时省略await ,都会破坏同步链。 The operation happens truly asynchronously, rather than "resynchronizing" through the await . 该操作实际上是异步发生的,而不是通过await “重新同步”。 The control is returned to the caller, while (sometime in the future) the operation resumes asynchronously. 控件返回给调用者,而(将来的某个时间)操作将异步恢复。

Remember, await is a return . 记住, awaitreturn It's only the consistent use of await that gives you the synchronization. 只有await的一致使用才能实现同步。 Stop using async void - change it to async Task and make sure you await the result properly. 停止使用async void - 将其更改为async Task并确保正确await结果。 The same goes for your MainAsync method. 您的MainAsync方法也是如此。 Task is the void of async methods. Taskvoid的异步方法。

There's only one case where you should ever see async void , and that's within a synchronization context of a legacy framework's event handlers (eg in Winforms). 只有一种情况,你应该看到async void ,这是在遗留框架的事件处理程序的同步上下文中(例如在Winforms中)。 If it's possible for an async method to return a Task , it really, really should. 如果async方法可以返回一个Task ,那么它确实应该。 Don't break the chain. 不要破坏链条。

The error is that your main function does not await for completion of procedure IncluiValores. 错误是您的主要功能不等待完成程序IncluiValores。 Your main program finishes before procedure IncluiValores is finished. 您的主程序在程序IncluiValores完成之前完成。

Because of this error I assume you still have some trouble understanding what happens when you use async-await. 由于此错误,我假设您在使用async-await时仍然无法理解会发生什么。

Someone here on StackOverflow (alas I can't find it anymore), explained it to me using the following metaphor. StackOverflow上有人(唉,我再也找不到了),用以下的比喻向我解释。

ADDITION: I found the metaphore 附加:我发现了这个比喻
It is in this interview with Eric Lippert 这是Eric Lippert的采访
Search somewhere in the middle for async-await 在中间某处搜索async-await
End Adition 结束了

Suppose you need to make breakfast. 假设你需要做早餐。 You want to toast some bread, boil some eggs and make some tea. 你想要烤面包,煮鸡蛋和泡茶。

Synchronous 同步

  • Put bread in the toaster and wait until the bread is toasted 将面包放入烤面包机中,等待面包烤好
  • Remove the bread from the toaster. 从烤面包机上取下面包。
  • Start boiling water, wait until the water boils 开始沸水,等到水沸腾
  • Put some eggs in the boiling water and wait 7 minutes until your eggs are ready 将一些鸡蛋放入沸水中等待7分钟,直到鸡蛋准备好
  • Remove the eggs from the water 从水中取出鸡蛋
  • Start boiling water for your tea and wait until the water boils 开始为你的茶开水,等到水沸腾
  • When the water boils you put it in the teapot and add some tea leaves and wait 4 minutes 当水煮沸时,你把它放入茶壶,加入一些茶叶,等待4分钟
  • Finally you get everything together and bring it to your breakfast table. 最后,你把所有东西放在一起,然后把它带到你的早餐桌上

You see you do a lot of waiting which is a waste of time, not to mention that your bread is probably cold by the time the tea is finished. 你看到你做了很多等待,这是浪费时间,更不用说你的面包在茶完成时可能很冷。

It would be much more efficient if you didn't wait all the time, but would start things simultaneously 如果你不是一直等待会更有效率,但会同时开始

using async-await: asynchronous using one thread 使用async-await:使用一个线程进行异步

  • Start as in the synchronous case: Put bread in the toaster 从同步情况开始:将面包放入烤面包机
  • but now you don't wait until the bread is toasted. 但现在你不要等到面包烤了。 Remember what you should do when the bread is toasted (remember this as Task A) 记住当烤面包时你应该做什么(记住这是任务A)
  • Start boiling water, but do not wait for the water to boil. 开始沸水,但不要等待水煮沸。 Remember what you should do when the water boils (remember this as Task B) 记住当水沸腾时你应该做什么(记住这是任务B)
  • Start boiling water for your tea, but do not wait for the waiter to boil. 开始为你的茶开水,但不要等待服务员煮沸。 Remember what you should do when the tea kettle boils (remember this as Task C) 记住当茶壶沸腾时你应该做什么(记住这个任务C)

  • Wait until any of the Tasks A / B / C is finished. 等到任务A / B / C完成任务。 Continue what you remembered what you should do when the task was finished. 继续你记得任务完成后你应该做什么。 If this needs some other waiting (time for the eggs or the tea to be ready), do not wait for it, but remember it as Tasks D and E and start waiting for all not finished tasks. 如果这需要一些其他等待(鸡蛋或茶准备好的时间),不要等待它,但记住它作为任务D和E并开始等待所有未完成的任务。

Note that in this method there is still only one person doing all the stuff. 请注意,在这种方法中,仍然只有一个人在做所有的事情。 If you use async-await this way, there is only one thread involved. 如果您使用async-await,则只涉及一个线程。 This thread is only waiting if it really has nothing to do. 这个线程只是等待它真的无关。 The advantage of this is that you don't face the problems you normally encounter when using several threads. 这样做的好处是您不会遇到使用多个线程时通常遇到的问题。

Asynchronous using several threads 异步使用多个线程

You could hire several cooks: one to toast bread and one to boil eggs while you make the tead. 你可以雇几个厨师:一个是吐司面包,另一个是煮鸡蛋。 This is an expensive method: start several threads, while the threads are doing nothing but wait most of the time. 这是一种昂贵的方法:启动多个线程,而线程什么都不做,只是等待大部分时间。 You also have the problems that the three cooks have to synchronize to make sure that they don't use the one-fire stove at the same time. 您还有三个厨师必须同步的问题,以确保他们不会同时使用单火炉。

Stephen Cleary wrote a comprehensive article that describes this async-await behaviour in Async and Await (Thank you Stephen!) Stephen Cleary撰写了一篇全面的文章,描述了Async和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();
}

You'll probably know by now how to make tea. 你现在可能知道如何泡茶。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM