使用异步方法

时间:2015-07-26 22:22:24

标签: c# asynchronous struct async-await

我刚刚在结构中使用异步方法遇到了一种奇怪的行为。有人可以解释为什么会这样,最重要的是如果有解决方法吗?这是一个简单的测试结构,仅仅是为了演示问题

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。为什么?有没有办法为结构编写异步方法并更改结构字段的值?

1 个答案:

答案 0 :(得分:16)

  

为什么?

由于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;

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

在处理可变结构时,这有点像预期的行为。这就是他们tend to be evil的原因。

你如何解决这个问题?由于我没有看到此行为发生变化,因此您必须创建class而不是struct

修改

将此作为issue on GitHub发布。收到了来自@AlexShvedov的受过良好教育的回复,它更深刻地解释了结构和状态机的复杂性:

  

由于每个闭包的执行都可以任意延迟,我们需要   某种方式也可以延迟捕获的所有成员的生命周期   关闭。对于这种值类型,通常无法做到这一点,   因为值类型可以在堆栈上分配(值的局部变量   类型)和堆栈空间将在方法执行退出时重用。

     

理论上,当值类型存储为某些托管的字段时   对象/数组元素,C#可以发出关闭代码来做结构   突变到位。不幸的是,没有关于这方面的知识   当发出struct member代码时,value被定位,所以C#决定了   只是强制用户手动处理这种情况(通过复制   大多数情况下此值,如建议的错误消息)。