await async异步方法后读取其先前设置的属性是否安全?
结论
你给出的示例场景下,await方法执行完成后访问对应属性完全安全,两个属性的值都保证是"Completed",不需要额外加锁。
原理说明
await的核心语义就是暂停当前方法的执行,直到等待的异步任务全部逻辑执行完成后,才会继续跑await后面的代码。这个保证和异步方法内部修改的是方法返回值、当前类的成员属性,还是传入参数的属性没有任何关系——只要你正确await了任务,任务内部的所有赋值操作都会在后续代码执行前完成,不存在执行顺序错乱的问题。- 任务并行库(TPL)在await节点前后天然做了内存屏障处理,不会出现CPU缓存、指令重排导致的写入不可见问题,不需要额外加锁或者加
volatile修饰来保证值的可见性。
为什么常见示例都用返回值传递结果
这只是编码实践层面的偏好,不是正确性的强制要求:
- 用返回值传递结果是无副作用的写法,可读性更强,调用方一眼就能明确方法的输出,不需要隐式依赖类成员、传入参数的隐式修改
- 这种写法更便于单元测试,也能从写法上减少不小心共享变量导致的竞态bug
- 这种惯例不代表「异步方法内给成员、参数属性赋值」的写法在正确await的前提下是错误或者不安全的。
需要加锁的例外场景
只有出现以下情况时,才需要考虑加锁或者其他同步机制:
- 没有await异步任务,任务还在执行过程中就提前访问被修改的属性
- 同时启动多个并发执行的异步任务,多个任务会同时修改同一个对象的同一个属性
- 异步流程中把对象引用传递给了其他不受await管控的独立线程/任务,没有等待这些执行流完成就访问属性
对应你给出的示例代码,两个Console.WriteLine的执行结果都是确定的:
class TestClass { string Member {get; set; } public class Parameter { public string Property { get; set; } } public async Task MethodAsync(Parameter parameter) { await Task.Delay(1000); Member = "Completed"; parameter.Property = "Completed"; } public async Task RunTestAsync() { Member = "Started"; var parameter = new Parameter() { Property = "Started" }; await MethodAsync(parameter); Console.WriteLine(Member); // 一定输出Completed,无安全问题 Console.WriteLine(parameter.Property); // 一定输出Completed,无安全问题 } }
内容的提问来源于stack exchange,提问作者Kris
相关产品推荐
相关产品推荐

