【问题标题】:Struct's private field value is not updated using an async method结构的私有字段值未使用异步方法更新
【发布时间】:2016-09-24 04:57:28
【问题描述】:

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

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

【问题讨论】:

  • 解决方法:不要使用可变结构开始。基本上,我怀疑您的结构被装箱以发生回调,并且突变 is 正在发生,但在该框中。但实际上,不要使用可变结构。这是一个有趣的(对我而言)关于如何被它们咬伤的变体,但是有很多这样的陷阱。
  • 我想指出,在我的情况下,使结构不可变不是一个选项。整个系统很久以前就编码了,我们只需要使用异步方法扩展某些部分。在这一点上重写整个事情看起来并不可行,但我完全理解让结构不可变的概念。有更便宜的解决方法吗?
  • 这可能是它的一个变体:stackoverflow.com/questions/29230626/… - 如果是,那么 Microsoft 已经确认枚举器/movenext 问题是编译器中的一个错误,他们在 Roslyn 中修复了这个问题。因此,如果在 Visual Studio 2015 中编译,看看这个问题是否仍然存在将会很有趣。
  • 单个内部 mutable 字段 - Microsoft(以及其他)仍然建议它们是类:Choosing between Class and Struct

标签: c# asynchronous struct async-await


【解决方案1】:

为什么?

因为您的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 的受过良好教育的回复,其中更深入地解释了结构和状态机的复杂性:

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

理论上,当值类型存储为某个托管的字段时 数组的对象/元素,C#可以发出闭包代码来做结构 就地突变。不幸的是,不知道这在哪里 value 是在发出 struct 成员代码时定位的,所以 C# 决定 只是强制用户手动处理这种情况(通过复制 大部分时间都是这个值,正如错误消息所建议的那样)。

【讨论】:

    猜你喜欢
    • 2014-04-04
    • 2015-12-10
    • 2013-05-07
    • 1970-01-01
    • 2015-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-04
    相关资源
    最近更新 更多