当涉及到可变值类型时,如何处理async / await产生的副作用?

请考虑以下示例代码:

using System.Diagnostics; using System.Threading.Tasks; public struct AStruct { public int Value; public async Task SetValueAsync() { Value = await Task.Run(() => 1); } public void SetValue() { Value = 1; } } class Program { static void Main(string[] args) { Test(new AStruct()); TestAsync(new AStruct()).Wait(); } private static async Task TestAsync(AStruct x) { Debug.Assert(x.Value == 0); await x.SetValueAsync(); Debug.Assert(x.Value == 0); } private static void Test(AStruct x) { Debug.Assert(x.Value == 0); x.SetValue(); Debug.Assert(x.Value == 1); } } 

注意TestTestAsync之间的区别。 此代码满足所有断言。

我想用reflection器查看代码会告诉我原因,但这仍然是我根本没想到的。

当然,将AStruct更改为类而不是结构会导致TestAsync的第二个断言失败 – 正如我所期望的那样。

我的问题是这个 – 除了不使用async / await的可变结构之外是否有一种优雅的方式使它们和平共存?

structasync方法本身不可能突变“本身”。

当你想到它时,这当然是完全有道理的。 当你在该结构中await的任何任务实际完成时,假定你已经返回到调用者并允许他们继续执行各种各样的事情,那么你无法确保调用它的实际结构实例方法甚至存在了。 如果SetValueAsync是通过一个没有await它的方法在本地变量上调用或Wait它或类似的东西那么该局部变量的生命周期很可能在SetValueAsync达到它对Run的调用的延续时结束。 它不能改变变量的寿命可能是也可能不在范围内。 这里唯一的选择是结构的async方法在调用方法时有效地复制自身,并使延续引用中的代码与调用async的变量完全不同。 由于该方法正在制作一个副本,除了这个async方法的主体之外不能访问它,这意味着,对于所有意图的目的,结构的async方法永远不能改变该结构(并且具有突变任何人都可以看到)。

你可以拥有一个可变structasync方法,只要该方法本身不会改变struct 。 这个方法需要返回一个带有新结构或等效的结构的Task

作为一个有趣的tanget,如果它真的想要的话,在方法的第一次await之前, structasync方法在技术可能性的范围内变异。 编译器选择立即获取副本,因此这实际上是不可能的,但是明确的选择是在方法的最开始而不是仅在第一次await之后进行复制。 这可能是最好的,无论是否是故意的决定,否则将是超级混乱。