简体   繁体   English

ForEach lambda异步与Task.WhenAll

[英]ForEach lambda async vs Task.WhenAll

I have an async method like this: 我有一个这样的异步方法:

private async Task SendAsync(string text) {
  ...
}

I also have to use this method one time for each item in a List: 对于列表中的每个项目,我还必须使用此方法一次:

List<string> textsToSend = new Service().GetMessages();

Currently my implementation is this: 目前,我的实现是这样的:

List<string> textsToSend = new Service().GetMessages();
List<Task> tasks = new List<Task>(textsToSend.Count);
textsToSend.ForEach(t => tasks.Add(SendAsync(t)));
await Task.WhenAll(tasks);

With this code, I get a Task for each message that runs async the sending method. 通过此代码,我为运行async发送方法的每条消息获取一个Task

However, I don't know if is there any different between my implementation and this one: 但是,我不知道我的实现与这个实现之间是否有任何区别:

List<string> textsToSend = new Service().GetMessages();
textsToSend.ForEach(async t => await SendAsync(t));

In the second one, I don't have the List<Task> allocation, but I think that the first one launch all Task in parallel and the second sample, one by one. 在第二个示例中,我没有List<Task>分配,但是我认为第一个示例并行启动所有Task ,第二个示例一个接一个地启动。

Could you help me to clarify if is there any different between the first and second samples? 您能帮我澄清一下第一样本和第二样本之间是否有区别?

PD: I also know that C#8 supports foreach async, however I'm using C# 7 PD:我也知道C#8支持foreach异步,但是我正在使用C#7

You don't even need a list, much less ForEach to execute multiple tasks and await all of them. 您甚至不需要列表,更不用说ForEach来执行多个任务并等待所有任务。 In any case, ForEach is just a convenience function that uses `foreach. 无论如何, ForEach只是使用`foreach的便捷功能。

To execute some async calls concurrently bases on a list of inputs all you need is Enumerable.Select . 要基于输入列表并发执行一些异步调用,您需要的只是Enumerable.Select To await all of them to complete you only need Task.WhenAll : 要等待他们全部完成,您只需要Task.WhenAll

var tasks=textsToSend.Select(text=>SendAsync(text));
await Task.WhenAll(tasks);

LINQ and IEnumerable in general use lazy evaluation which means Select 's code won't be executed until the returned IEnumerable is iterated. LINQ和IEnumerable通常使用惰性评估,这意味着直到对返回的IEnumerable进行迭代之前, Select的代码才会执行​​。 In this case it doesn't matter because it's iterated in the very next line. 在这种情况下,这并不重要,因为它在下一行进行了迭代。 If one wanted to force all tasks to start a call to ToArray() would be enough, eg : 如果要强制所有任务开始调用ToArray()就足够了,例如:

var tasks=textsToSend.Select(SendAsync).ToArray();

If you wanted to execute those async calls sequentially, ie one after the other, you could use a simple foreach. 如果要顺序(即一个接一个地)执行这些异步调用,则可以使用简单的foreach。 There's no need for C# 8's await foreach : 不需要C#8 await foreach

foreach(var text in textsToSend)
{
    await SendAsync(text);
}

The Bug 错误

This line is simply a bug : 这行只是一个错误:

textsToSend.ForEach(async t => await SendAsync(t));

ForEach doesn't know anything about tasks so it never awaits for the generated tasks to complete. ForEach对任务一无所知,因此它永远不会等待生成的任务完成。 In fact, the tasks can't be awaited at all. 实际上,根本就不能等待任务。 The async t syntax creates an async void delegate. async t语法创建一个async void委托。 It's equivalent to : 等效于:

async void MyMethod(string t)
{
    await SendAsync(t);
}

textToSend.ForEach(t=>MyMethod(t));

This brings all the problems of async void methods. 这带来了async void方法的所有问题。 Since the application knows nothing about those async void calls, it could easily terminate before those methods complete, resulting in NREs, ObjectDisposedException s and other weird problems. 由于应用程序对这些async void调用一无所知,因此它很容易在这些方法完成之前终止,从而导致NRE, ObjectDisposedException和其他奇怪的问题。

For reference check David Fowler's Implicit async void delegates 供参考,请参阅David Fowler的隐式异步void委托

C# 8 and await foreach C#8,等待foreach

C# 8's IAsyncEnumerable would be useful in the sequential case, if we wanted to return the results of each async operation in an iterator, as soon as we got them. 如果我们想在迭代器中尽快返回每个异步操作的结果,则在顺序情况下,C#8的IAsyncEnumerable将很有用。

Before C# 8 there would be no way to avoid awaiting for all results, even with sequential execution. 在C#8之前,即使有顺序执行,也无法避免等待所有结果。 We'd have to collect all of them in a list. 我们必须将所有这些收集在一个列表中。 Assuming each operation returned a string, we'd have to write : 假设每个操作都返回一个字符串,我们将不得不编写:

async Task<List<string> SendTexts(IEnumerable<string> textsToSend)
{
    var results=new List<string>();
    foreach(var text in textsToSend)
    {
        var result=await SendAsync(text);
        results.Add(result);
    }
}

And use it with : 并与使用:

var results=await SendTexts(texts);

In C# 8 we can return individual results and use them asynchronously. 在C#8中,我们可以返回单个结果并异步使用它们。 We don't need to cache the results before returning them either : 在返回结果之前,我们不需要缓存结果:

async IAsyncEmumerable<string> SendTexts(IEnumerable<string> textsToSend)
{
    foreach(var text in textsToSend)
    {
        var result=await SendAsync(text);
        yield return;
    }
}


await foreach(var result in SendTexts(texts))
{
   ...
}

await foreach is only needed to consume the IAsyncEnumerable result, not produce it 只需要await foreach消耗 IAsyncEnumerable结果,而不产生它

that the first one launch all Task in parallel 第一个并行启动所有任务

Correct. 正确。 And await Task.WhenAll(tasks); await Task.WhenAll(tasks); waits for all messages are sent. 等待所有消息发送。

The second one also sends messages in parallel but doesn't wait for all messages are sent since you don't await any task. 第二个也并行发送消息,但是由于您不等待任何任务,因此不等待所有消息发送完毕。

In your case: 在您的情况下:

textsToSend.ForEach(async t => await SendAsync(t));

is equivalent to 相当于

textsToSend.ForEach(t => SendAsync(t));

the async t => await SendAsync(t) delegate may return the task (it depends on assignable type) as SendAsync(t) . async t => await SendAsync(t)委托可以将任务(取决于可分配类型)作为SendAsync(t) In case of passing it to ForEach both async t => await SendAsync(t) and SendAsync(t) will be translated to Action<string> . 在将其传递给ForEach情况下, async t => await SendAsync(t)SendAsync(t)转换为Action<string>

Also the first code will throw an exception if any SendAsync throws an excepion. 同样,如果任何SendAsync抛出异常,则第一个代码将引发异常。 In the second code any exception will be ignored. 在第二个代码中,任何异常都将被忽略。

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

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