Struct'的私有字段值没有使用异步方法更新

本文关键字:异步方法 更新 字段 Struct | 更新日期: 2023-09-27 18:05:02

我刚刚遇到了在结构体中使用异步方法的奇怪行为。有人能解释一下为什么会发生这种情况吗?最重要的是,是否有解决办法?下面是一个简单的测试结构,只是为了演示问题

public struct Structure 
{
   private int _Value;
   public Structure(int iValue) 
   {
      _Value = iValue;
   }
   public void Change(int iValue)
   {
      _Value = iValue;
   }
   public async Task ChangeAsync(int iValue)
   {
      await Task.Delay(1);
      _Value = iValue;
   }
}
现在,让我们使用该结构并执行以下调用
var sInstance = new Structure(25);
sInstance.Change(35);
await sInstance.ChangeAsync(45);

第一行实例化结构,sInstance._Value的值为25。第二行更新sInstance._Value值,变为35。现在第三行没有做任何事情,但我希望它将sInstance._Value值更新为45,但是sInstance._Value保持35。为什么?是否有一种方法来写一个异步方法的结构和改变结构字段的值?

Struct'的私有字段值没有使用异步方法更新

为什么?

因为您的struct被提升到状态机的方式。

ChangeAsync 实际上是的样子:

[DebuggerStepThrough, AsyncStateMachine(typeof(Program.Structure.<ChangeAsync>d__4))]
public Task ChangeAsync(int iValue)
{
    Program.Structure.<ChangeAsync>d__4 <ChangeAsync>d__;
    <ChangeAsync>d__.<>4__this = this;
    <ChangeAsync>d__.iValue = iValue;
    <ChangeAsync>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
    <ChangeAsync>d__.<>1__state = -1;
    AsyncTaskMethodBuilder <>t__builder = <ChangeAsync>d__.<>t__builder;
    <>t__builder.Start<Program.Structure.<ChangeAsync>d__4>(ref <ChangeAsync>d__);
    return <ChangeAsync>d__.<>t__builder.Task;
}

重要的一行是:

<ChangeAsync>d__.<>4__this = this;

编译器将struct的副本提升到它的状态机中,有效地将它的副本更新为值45。当异步方法完成时,它已经改变了副本,而结构体的实例保持不变。

这在某种程度上是在处理可变结构时的预期行为。这就是为什么他们倾向于邪恶。

你怎么解决这个问题?因为我没有看到这种行为改变,你必须创建一个class而不是struct

编辑:

将此作为GitHub上的问题发布。收到了@AlexShvedov的回复,他更深入地解释了结构体和状态机的复杂性:

由于每个闭包的执行可以任意延迟,因此需要某种方式还可以延迟所有成员的生命周期捕获到关闭。对于value类型的this,一般没有办法这样做,由于值类型可以在栈上分配值的局部变量类型)和堆栈空间将在方法执行退出时重用。

理论上,当值类型存储为某种托管的字段时对象/数组元素,c#可以发出闭包代码来做struct变异原地。不幸的是,没有人知道这是在哪里值在发出结构成员代码时定位,因此c#决定强制用户手动处理这种情况(通过复制这个值在大多数情况下(如错误消息所示)。