c# - 仅使用 async/await 时是否存在竞争条件?

标签 c# asynchronous async-await race-condition .net-5

.NET 5.0

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;
using System;
using System.Collections.Generic;

namespace AsyncTest
{
    [TestClass]
    public class AsyncTest
    {
        public async Task AppendNewIntVal(List<int> intVals)
        {
            await Task.Delay(new Random().Next(15, 45));

            intVals.Add(new Random().Next());
        }

        public async Task AppendNewIntVal(int count, List<int> intVals)
        {
            var appendNewIntValTasks = new List<Task>();

            for (var a = 0; a < count; a++)
            {
                appendNewIntValTasks.Add(AppendNewIntVal(intVals));
            }

            await Task.WhenAll(appendNewIntValTasks);
        }

        [TestMethod]
        public async Task TestAsyncIntList()
        {
            var appendCount = 30;
            var intVals = new List<int>();

            await AppendNewIntVal(appendCount, intVals);

            Assert.AreEqual(appendCount, intVals.Count);
        }
    }
}
上面的代码编译并运行,但测试失败,输出类似于:

Assert.AreEqual failed. Expected:<30>. Actual:<17>.


在上面的示例中,“实际”值为 17,但它在执行之间有所不同。
我知道我对异步编程在 .NET 中的工作方式缺乏一些了解,因为我没有得到预期的输出。
据我了解,AppendNewIntVal方法启动 N 个任务,然后等待它们全部完成。如果他们都完成了,我希望他们每个人都会在列表中附加一个值,但事实并非如此。看起来存在竞争条件,但我认为这是不可能的,因为代码不是多线程的。我错过了什么?

最佳答案

是的,如果您不立即等待每个可等待的,即在这里:

appendNewIntValTasks.Add(AppendNewIntVal(intVals));
这一行在异步方面相当于(在基于线程的代码中)Thread.Start ,我们现在对内部异步代码没有安全感:
intVals.Add(new Random().Next());
当两个流调用 Add 时,现在可以以相同的并发方式失败同时。你也应该避免 new Random() ,因为这不一定是随机的(它是基于许多框架版本的时间,最终可能有两个流获得相同的种子)。
所以:所示的代码确实很危险。
明显安全的版本是:
        public async Task AppendNewIntVal(int count, List<int> intVals)
        {
            for (var a = 0; a < count; a++)
            {
                await AppendNewIntVal(intVals);
            }
        }
可以推迟 await ,但是当您这样做时,您明确选择了并发,并且您的代码需要适本地防御性地处理它。

关于c# - 仅使用 async/await 时是否存在竞争条件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68222989/

相关文章:

c# - 如何通过部分名称查找文件?

javascript - React JS 异步重计算

javascript - 在清除的字符串中异步连接点击的数字

java - 如何将此事件方法从 C# 转换为 java?

c# - 在windows sdk中没有找到引用(Windows 1903)

c# - ADO.Net 中的异步编程

c# - Task.WhenAll 的顺序版本

c# - 为什么 Elvis (?.) 运算符不适用于异步等待?

javascript - 在 while 循环中使用 wait 是一个坏习惯吗

c# - 递归遍历控件在 OnInit 中禁用 GridView RowCommand 并丢失 Viewstate