小心使用 List 的 ForEach 方法

小心使用 List 的 ForEach 方法

Intro

最近在我们的项目中发现有使用 list 的 ForEach 方法,并且 ForEach 里的是一个异步方法,这导致原本我们想要等待 ForEach 中的任务完成之后再继续其他另外一个 task,但是出现了 ForEach 的 task 还未完成,另外一个 task 已经开始执行了

Sample

前面说的可能有些绕,我们来看个示例

public static void MainTest()
{
    var list = Enumerable.Range(1, 5).ToList();

    list.ForEach(async i =>
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        Console.WriteLine($"SubTask {i} completed.");
    });

    Console.WriteLine("Task completed");
    Console.ReadLine();
}

大家可以先猜一下输出结果是什么哈

我们预期的结果应该是上面 ForEach 里的 task 都完成之后再输出 Task completed

但实际上并非如此,输出结果如下(SubTask 输出的顺序可能会有不同,但应该都是先输出 Task completed)

f1a2ab1331dc844c682b3486fc6d0680.png

ForEach output

为什么会出现这样的结果呢,为什么没有按照预期等 sub task 完成呢

我们可以看下这个方法的签名,可以参考微软文档:https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.foreach?view=net-8.0

public void ForEach (Action<T> action);

可以看到 ForEach 这个方法的参数是一个 Action<T> 是一个同步的委托方法,如果我们在里面写了一个异步方法是不会等待的,这等同于委托是一个 async void 的方法,这就会导致在 await 的时候 subTask 并未结束就已经返回开始执行其他的逻辑了

如何修改让他符合我们的预期呢,我们可以使用 foreach 遍历来代替

public static async Task MainTestAsync()
{
    var list = Enumerable.Range(1, 5).ToList();

    foreach (var i in list)
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        Console.WriteLine($"SubTask {i} completed.");
    }

    Console.WriteLine("Task completed");
    Console.ReadLine();
}

输出结果如下:

29574103e0c8a8f04c1dc0ef1e6eac90.png

foreach output

More

如果 ForEach 的委托是异步方法需要等待的话,请使用 foreach 来代替

类似的还有别的方法参数是 action 时,最好不要用异步,否则不会等待任务完成,某些场景下会出现一些 BUG

如果要等待异步方法的完成就需要使用 Func<Task> 代替 Action, Func<T, Task> 代替 Action<T> ...

References

  • https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.foreach?view=net-8.0

  • https://github.com/WeihanLi/SamplesInPractice/blob/master/BalabalaSample/ListForEachSample.cs

-

技术群:添加小编微信并备注进群

小编微信:mm1552923   

公众号:dotNet编程大全    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值