如何在ForEach中使用Async?

Jam*_*ery 100 c# async-await

使用ForEach时是否可以使用Async?以下是我正在尝试的代码:

using (DataContext db = new DataLayer.DataContext())
{
    db.Groups.ToList().ForEach(i => async {
        await GetAdminsFromGroup(i.Gid);
    });
}
Run Code Online (Sandbox Code Playgroud)

我收到错误:

当前上下文中不存在名称"Async"

包含using语句的方法设置为async.

Ste*_*ary 156

List<T>.ForEachasync由于同样的原因,LINQ-to-objects 也没有特别好用.

在这种情况下,我建议将每个元素投射到异步操作中,然后您可以(异步)等待它们全部完成.

using (DataContext db = new DataLayer.DataContext())
{
    var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
    var results = await Task.WhenAll(tasks);
}
Run Code Online (Sandbox Code Playgroud)

这种方法比给予async代表的好处ForEach是:

  1. 错误处理更合适.例外async void不能被抓住catch; 这种方法将在该await Task.WhenAll行传播异常,允许自然异常处理.
  2. 您知道在此方法结束时任务已完成,因为它执行了此操作await Task.WhenAll.如果使用async void,则无法轻易判断操作何时完成.
  3. 此方法具有检索结果的自然语法.GetAdminsFromGroupAsync听起来这是一个产生结果的操作(管理员),如果这样的操作可以返回结果而不是将值设置为副作用,则此类代码更自然.

  • 并不是它改变了什么,但`List.ForEach()`不是LINQ的一部分. (4认同)
  • @StewartAnderson:任务将同时执行.串行执行没有扩展; 只需在你的循环体中用`await`做一个`foreach`. (4认同)
  • @mare:`ForEach`仅采用同步委托类型,并且没有采用异步委托类型的重载。所以简短的回答是“没有人编写异步`ForEach`”。更长的答案是你必须假设一些语义;例如,这些项目应该一次处理一个(如“foreach”),还是同时处理(如“Select”)?如果一次一个,异步流不是更好的解决方案吗?如果同时进行,结果应该按原始项目顺序还是按完成顺序?它应该在第一次失败时失败还是等到所有失败都完成?ETC。 (4认同)
  • @RogerWolf:是的;使用“SemaphoreSlim”来限制异步任务。 (2认同)

JD *_*toy 50

这个小扩展方法应该为您提供异常安全的异步迭代:

public static async Task ForEachAsync<T>(this List<T> list, Func<T, Task> func)
{
    foreach (var value in list)
    {
        await func(value);
    }
}
Run Code Online (Sandbox Code Playgroud)

由于我们正在将lambda的返回类型更改voidTask,因此异常将正确传播.这将允许你在实践中写这样的东西:

await db.Groups.ToList().ForEachAsync(async i => {
    await GetAdminsFromGroup(i.Gid);
});
Run Code Online (Sandbox Code Playgroud)


Rub*_*uck 22

简单的答案是使用foreach关键字而不是 的ForEach()方法List()

using (DataContext db = new DataLayer.DataContext())
{
    foreach(var i in db.Groups)
    {
        await GetAdminsFromGroup(i.Gid);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 为什么这个可以工作,而不是使用“List()”的“ForEach()”方法? (2认同)
  • 我其实认为这是最简单的方法 (2认同)

And*_*ski 19

从 开始C# 8.0,您可以异步创建和使用流。

    private async void button1_Click(object sender, EventArgs e)
    {
        IAsyncEnumerable<int> enumerable = GenerateSequence();

        await foreach (var i in enumerable)
        {
            Debug.WriteLine(i);
        }
    }

    public static async IAsyncEnumerable<int> GenerateSequence()
    {
        for (int i = 0; i < 20; i++)
        {
            await Task.Delay(100);
            yield return i;
        }
    }
Run Code Online (Sandbox Code Playgroud)

更多的

  • 这缺乏如何将其应用于 OP 问题的解释/示例。 (14认同)
  • 这样做的优点是,除了等待每个元素之外,您现在还等待枚举器的“MoveNext”。这在枚举器无法立即获取下一个元素而必须等待一个元素可用的情况下非常重要。 (2认同)

小智 13

这不是一个老问题,但.Net 6引入了Parallel.ForeachAsync

var collectionToIterate = db.Groups.ToList();
await Parallel.ForEachAsync(collectionToIterate, async (i, token) =>
{
    await GetAdminsFromGroup(i);
});
Run Code Online (Sandbox Code Playgroud)

ForeachAsync 还接受 ParallelOptions 对象,但通常您不想弄乱MaxDegreeOfParallelism 属性:

ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 4 };
var collectionToIterate = db.Groups.ToList();

await Parallel.ForEachAsync(collectionToIterate, parallelOptions , async (i, token) =>
{
    await GetAdminsFromGroup(i);
});
Run Code Online (Sandbox Code Playgroud)

来自微软文档:https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions.max Degreeofparallelism ?view=net-6.0

默认情况下,For 和 ForEach 将利用底层调度程序提供的线程数,因此更改默认值的 MaxDegreeOfParallelism 仅限制将使用的并发任务数。

一般来说,你不需要修改这个设置......


小智 8

以下是具有顺序处理的上述异步foreach变体的实际工作版本:

public static async Task ForEachAsync<T>(this List<T> enumerable, Action<T> action)
{
    foreach (var item in enumerable)
        await Task.Run(() => { action(item); }).ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)

这是实施:

public async void SequentialAsync()
{
    var list = new List<Action>();

    Action action1 = () => {
        //do stuff 1
    };

    Action action2 = () => {
        //do stuff 2
    };

    list.Add(action1);
    list.Add(action2);

    await list.ForEachAsync();
}
Run Code Online (Sandbox Code Playgroud)

关键区别是什么?.ConfigureAwait(false);它保持主线程的上下文,同时对每个任务进行异步顺序处理.


Jam*_*ery -2

问题是async关键字需要出现在 lambda 之前,而不是主体之前:

db.Groups.ToList().ForEach(async (i) => {
    await GetAdminsFromGroup(i.Gid);
});
Run Code Online (Sandbox Code Playgroud)

  • 这是对“async void”的不必要且微妙的使用。这种方法在异常处理和了解异步操作何时完成方面存在问题。 (50认同)