不使用异步方法更新Struct的私有字段值
我刚刚在结构中使用异步方法遇到了一个奇怪的行为。 有人可以解释为什么会发生这种情况,最重要的是如果有解决方法吗? 这是一个简单的测试结构,仅仅是为了演示问题
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.d__4))] public Task ChangeAsync(int iValue) { Program.Structure. d__4 d__; d__.<>4__this = this; d__.iValue = iValue; d__.<>t__builder = AsyncTaskMethodBuilder.Create(); d__.<>1__state = -1; AsyncTaskMethodBuilder <>t__builder = d__.<>t__builder; <>t__builder.Startd__4>(ref d__); return d__.<>t__builder.Task; }
重要的是这个:
d__.<>4__this = this;
编译器将结构的副本提升到其状态机,有效地使用值45更新其副本。当异步方法完成时,它已经改变了副本,而结构的实例保持不变。
在处理可变结构时,这有点是预期的行为 。 这就是为什么他们往往是邪恶的 。
你怎么解决这个问题? 由于我没有看到这种行为发生变化,因此您必须创建一个class
而不是struct
。
编辑:
在GitHub上发布此问题 。 收到了来自@AlexShvedov的受过良好教育的回复,它解释了结构和状态机的复杂性:
由于每个闭包的执行都可以任意延迟,我们需要一些方法来延迟捕获到闭包的所有成员的生命周期。 对于value类型,通常无法做到这一点,因为值类型可以在堆栈上分配(值类型的局部变量),堆栈空间将在方法执行退出时重用。
理论上,当值类型存储为某个托管对象/数组元素的字段时,C#可以发出封闭代码来进行结构变异。 不幸的是,在发出struct member代码时,不知道这个值的位置,所以C#决定只是强制用户手动处理这种情况(通过大多数时间复制这个值,如建议的错误消息)。