请考虑以下示例代码:
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);
}
}
注意Test
和TestAsync
之间的区别。此代码满足所有断言。
我想用反射器查看代码会告诉我原因,但这仍然是我根本没想到的。
当然,将AStruct
更改为类而不是结构会导致TestAsync
中的第二个断言失败 - 正如我所期望的那样。
我的问题是这个 - 除了不使用async / await的可变结构之外是否有一种优雅的方式使它们和平共存?
答案 0 :(得分:10)
async
的{{1}}方法本身不可能突变“本身”。
当你想到它时,这当然是完全有道理的。到那个结构内部struct
实际完成的任务时,假定你已经返回调用者并允许他们继续执行各种操作,那么就无法确保实际的结构调用该方法的实例甚至已经存在。如果await
是通过一个没有SetValueAsync
它或await
的方法在本地变量上调用的,那么那个局部变量的生命周期很可能已经结束了{ {1}}继续调用Wait
。它不能改变生命周期可能在也可能不在范围内的变量。这里唯一的选择是结构的SetValueAsync
方法在调用方法时有效地复制自己,并使延续引用中的代码与调用Run
的变量完全不同。由于该方法正在制作一个副本,除了这个async
方法的主体以外的任何地方都无法访问,这意味着,对于所有意图而言,结构的async
方法永远不会变异那个结构(并且任何人都可以看到变异)。
您可以拥有可变async
的{{1}}方法,只要该方法本身不会改变async
。这个方法需要返回一个带有新结构的async
或类似的东西。
作为一个有趣的方法,在方法的第一个struct
struct Task<T>
方法可以使自己变异的技术可能性范围内>如果它真的想。编译器选择立即获取副本,因此实际上这不可能,但是明确的选择是在方法的最开始而不是仅在第一个async
之后制作副本。这可能是最好的,无论是否是故意的决定,否则将是非常混乱。