C# 如何将Async与ForEach一起使用?

C# 如何将Async与ForEach一起使用?,c#,async-await,C#,Async Await,使用ForEach时可以使用Async吗?下面是我正在尝试的代码: using (DataContext db = new DataLayer.DataContext()) { db.Groups.ToList().ForEach(i => async { await GetAdminsFromGroup(i.Gid); }); } 我得到一个错误: 当前上下文中不存在名称“Async” using语句所包含的方法被设置为async。问题在于async关键

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

using (DataContext db = new DataLayer.DataContext())
{
    db.Groups.ToList().ForEach(i => async {
        await GetAdminsFromGroup(i.Gid);
    });
}
我得到一个错误:

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


using语句所包含的方法被设置为async。

问题在于
async
关键字需要出现在lambda之前,而不是正文之前:

db.Groups.ToList().ForEach(async (i) => {
    await GetAdminsFromGroup(i.Gid);
});
List.ForEach
不能很好地处理
async
(出于同样的原因,LINQ也不能处理对象)

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

using (DataContext db = new DataLayer.DataContext())
{
    var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
    var results = await Task.WhenAll(tasks);
}
与将
async
委托给
ForEach
相比,这种方法的好处是:

  • 错误处理更合适。
    catch
    无法捕获来自
    async void
    的异常;此方法将在
    wait Task.whalll
    行传播异常,允许自然异常处理
  • 您知道此方法结束时任务已完成,因为它执行
    等待任务。whalll
    。如果使用
    async void
    ,则无法轻松判断操作何时完成
  • 这种方法具有检索结果的自然语法
    GetAdminsFromGroupAsync
    听起来像是一个生成结果的操作(管理员),如果这样的操作可以返回结果,而不是将值设置为副作用,那么这样的代码更自然

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

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

    添加此扩展方法

    public static class ForEachAsyncExtension
    {
        public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
        {
            return Task.WhenAll(from partition in Partitioner.Create(source).GetPartitions(dop) 
                select Task.Run(async delegate
                {
                    using (partition)
                        while (partition.MoveNext())
                            await body(partition.Current).ConfigureAwait(false);
                }));
        }
    }
    

    以下是上述异步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);
    }
    
    公共静态异步任务ForEachAsync(此列表可枚举,操作)
    {
    foreach(可枚举中的变量项)
    等待任务。运行(()=>{action(item);});
    }
    
    以下是实施方案:

    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();
    }
    
    public async void SequentialAsync()
    {
    var list=新列表();
    动作1=()=>{
    //做事1
    };
    动作2=()=>{
    //做事2
    };
    增加(行动1);
    增加(行动2);
    wait list.ForEachAsync();
    }
    

    关键区别是什么<代码>.ConfigureAwait(false)在异步顺序处理每个任务时保留主线程的上下文。

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

    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;
            }
        }
    
    private async void按钮1\u单击(对象发送方,事件参数e)
    {
    IAsyncEnumerable enumerable=GenerateSequence();
    等待foreach(可枚举中的变量i)
    {
    Debug.WriteLine(i);
    }
    }
    公共静态异步IAsyncEnumerable GenerateSequence()
    {
    对于(int i=0;i<20;i++)
    {
    等待任务。延迟(100);
    收益率i;
    }
    }
    

    这是我创建的方法,用于使用
    ForEach
    处理异步场景

    • 如果其中一个任务失败,则其他任务将继续执行
    • 您可以添加将在每个异常上执行的函数
    • 异常将在末尾作为aggregateException收集,并可供您使用
    • 可以处理CancellationToken
    公共静态类并行执行器
    {
    /// 
    ///在具有任务计数限制的给定可枚举项的所有元素上异步执行给定函数。
    ///即使其中一个任务抛出,Executor也将继续启动新任务。如果至少有一个任务抛出异常,则在方法运行结束时抛出异常。
    /// 
    ///可枚举文件中元素的类型
    ///最大任务计数。
    ///可枚举的。
    ///将在可枚举的每个元素上执行的异步函数。必须是线程安全的。
    ///将对asyncFunc引发的每个异常执行的Acton可能是线程不安全的。
    ///取消令牌。
    公共静态异步任务ForEachAsync(int maxTaskCount、IEnumerable enumerable、Func asyncFunc、Action OneException=null、CancellationToken CancellationToken=default)
    {
    使用var信号量=新信号量lim(initialCount:maxTaskCount,maxCount:maxTaskCount);
    //此“lockObject”仅在“catch{}”块中使用。
    对象锁定对象=新对象();
    var exceptions=新列表();
    var tasks=新任务[enumerable.Count()];
    int i=0;
    尝试
    {
    foreach(可枚举中的var t)
    {
    wait semaphore.WaitAsync(cancellationToken);
    tasks[i++]=Task.Run(
    异步()=>
    {
    尝试
    {
    等待异步函数(t);
    }
    捕获(例外e)
    {
    if(onException!=null)
    {
    锁定(锁定对象)
    {
    onException.Invoke(e);
    }
    }
    //此异常将在此处被接受,但将在ForEachAsync方法的末尾收集,以生成AggregateException。
    投掷;
    }
    最后
    {
    semaphore.Release();
    }
    
    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();
    }
    
    using (DataContext db = new DataLayer.DataContext())
    {
        foreach(var i in db.Groups)
        {
            await GetAdminsFromGroup(i.Gid);
        }
    }
    
        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;
            }
        }