简体   繁体   中英

What is the proper way to await for async inside of a ForEach?

.NET Core 2.2, VS 2019

The problem is, the UpdateDatabaseWithFile is never getting executed? What am I doing wrong here? I tried to wrap Directory with an await, but I can't, since it returns void. What is the proper way to do this?

Here is the code:

Directory
   .GetFiles(@"C:\Temp")
   .ToList()
   .ForEach(async file =>
   {
        await this._dataService.UpdateDatabaseWithFile();
   });

It is because ForEach<T> just executes that async functions, but didn't wait for their completion.

You can use Select for projecting Task s which has to be executed and then use Task.WhenAll() to execute that functions and wait for their completion:

 var tasks = Directory
        .GetFiles(@"C:\Temp")
        .Select(async file =>
        {
            await this._dataService.UpdateDatabaseWithFile();
        });


 await Task.WhenAll(tasks);

Or if you want to execute functions sequentally, then instead of Task.WhenAll() you can use simple foreach :

 foreach (var file in Directory.GetFiles(@"C:\Temp"))
 {
      await this._dataService.UpdateDatabaseWithFile();
 }

Additional explanation:

Here is the implementation of ForEach<T> :

 for(int i = 0 ; i < array.Length; i++) {
       action(array[i]);
  }

As you see it just executes that action but didn't wait for their completion. And actually, it can't. Action returns nothing. But for waiting Task has to be returned. So, for making ForEach function useful in that situation, it has to take Func which return Task and then await inside for iterator..

The method List.ForEach doesn't understand async delegates, in other words it hasn't an overload that accepts a Func<Task<T>> parameter. In these cases what happens is that the supplied async lambda is treated as async void . Async voids are evil , because they can't be awaited, so their execution can't be coordinated with other parts of the program. They are similar to fire-and-forget tasks, with the added drawback of killing the process in case of an exception.

Void-returning async methods have a specific purpose: to make asynchronous event handlers possible.

You should pay attention to the signature of the method you are attempting to call with an async delegate as argument. Some methods like Task.Run have specific overloads for async delegates, and handle them well. Other methods like the LINQ Select are not designed with async await in mind, but at least they accept arguments of type Func instead of Action , and so the generated tasks are returned and can be collected and awaited properly. Methods like the List.ForEach with Action arguments should never be used with async delegates.

The most likely problem here is that .ForEach() provides no way for you to actually wait for the operations to complete. It will just execute them and move on.

Eric Lippert has a good blog post on why the .ForEach() method isn't all that great, and this is a perfect example of how a foreach loop excels over .ForEach - foreach will allow you to actually await the operations.

Just use a loop, and your problems go away:

foreach (var file = Directory.GetFiles(@"C:\Temp"))
{
    await this._dataService.UpdateDatabaseWithFile();
}

It should be noted that you don't seem to actually be using the file variable anywhere, and that seems like a problem. Hopefully just a typo?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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