C# 中 await 前后的跨线程数组修改是否安全?
结论
按题目中的调用方式,代码先后修改数组,调用方再执行 await DoRequestAndStoreResult(result),等待完成后读取数组,操作顺序是明确的。续体可能在另一个线程上运行,但线程切换不会让 await 前后的代码并行执行。异步方法完成后,调用方才会继续处理结果。
这里的顺序由异步操作的调度和完成关系决定。不能因此认为任意 await 都能保护所有共享数据,或充当通用内存屏障。
题目中的代码为什么能读到 123
第一段代码在 await Task.Yield() 之前写入 value[0],续体恢复后才读取它。Task.Yield() 会让出当前执行,续体可能被安排到线程池线程上运行。示例中的线程 ID 变化说明执行线程发生了变化,但不能证明出现了并行访问。
第二段代码的执行顺序是:方法先写入 data[0],经过 await Task.Yield() 后再写入 data[1];异步方法完成后,调用方的 await 返回,最后调用方读取 result。只要没有其他并发访问者修改数组,这些操作就会按异步操作的完成关系依次衔接。
什么时候会有数据竞争
如果另一个线程在这些步骤之间同时读写同一个数组,或者多个异步操作并发修改同一数据,就需要单独处理同步。await 不会自动锁住数组,也不会让其他线程按某个顺序访问它。
以下情况需要考虑同步方案:
- 多个请求同时修改同一个数组或对象。
- 一个线程读取数据时,另一个线程可能正在更新它。
- 多个字段必须保持整体一致,不能让读取者看到部分更新。
可以根据需求使用 lock、SemaphoreSlim、Interlocked、线程安全集合,或将数据设计成不可变对象,在完成后整体发布。volatile 不是通用的复合操作保护手段,不能让多次读写自动成为一个原子事务。
实际判断方法
先检查是否可能有两个执行流同时访问共享数据。如果调用方只在 await 等待异步方法完成后读取结果,且没有其他并发访问者介入,题目中的写法可以按这个顺序使用。如果有并发访问者,就应明确同步共享状态的读写,不能只依赖 async/await。
备注:内容仅供参考。
