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
被提升到状态机的方式。
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#决定强制用户手动处理这种情况(通过复制这个值在大多数情况下(如错误消息所示)。