当涉及到可变值类型时,如何处理async / await产生的副作用?

时间:2014-07-17 18:49:31

标签: c# struct async-await

请考虑以下示例代码:

using System.Diagnostics;
using System.Threading.Tasks;

public struct AStruct
{
    public int Value;

    public async Task SetValueAsync()
    {
        Value = await Task.Run(() => 1);
    }
    public void SetValue()
    {
        Value = 1;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Test(new AStruct());
        TestAsync(new AStruct()).Wait();
    }

    private static async Task TestAsync(AStruct x)
    {
        Debug.Assert(x.Value == 0);
        await x.SetValueAsync();
        Debug.Assert(x.Value == 0);
    }

    private static void Test(AStruct x)
    {
        Debug.Assert(x.Value == 0);
        x.SetValue();
        Debug.Assert(x.Value == 1);
    }
}

注意TestTestAsync之间的区别。此代码满足所有断言。

我想用反射器查看代码会告诉我原因,但这仍然是我根本没想到的。

当然,将AStruct更改为类而不是结构会导致TestAsync中的第二个断言失败 - 正如我所期望的那样。

我的问题是这个 - 除了不使用async / await的可变结构之外是否有一种优雅的方式使它们和平共存?

1 个答案:

答案 0 :(得分:10)

async的{​​{1}}方法本身不可能突变“本身”。

当你想到它时,这当然是完全有道理的。到那个结构内部struct实际完成的任务时,假定你已经返回调用者并允许他们继续执行各种操作,那么就无法确保实际的结构调用该方法的实例甚至已经存在。如果await是通过一个没有SetValueAsync它或await的方法在本地变量上调用的,那么那个局部变量的生命周期很可能已经结束了{ {1}}继续调用Wait。它不能改变生命周期可能在也可能不在范围内的变量。这里唯一的选择是结构的SetValueAsync方法在调用方法时有效地复制自己,并使延续引用中的代码与调用Run的变量完全不同。由于该方法正在制作一个副本,除了这个async方法的主体以外的任何地方都无法访问,这意味着,对于所有意图而言,结构的async方法永远不会变异那个结构(并且任何人都可以看到变异)。

您可以拥有可变async的{​​{1}}方法,只要该方法本身不会改变async。这个方法需要返回一个带有新结构的async或类似的东西。

作为一个有趣的方法,在方法的第一个struct struct Task<T>方法可以使自己变异的技术可能性范围内>如果它真的想。编译器选择立即获取副本,因此实际上这不可能,但是明确的选择是在方法的最开始而不是仅在第一个async之后制作副本。这可能是最好的,无论是否是故意的决定,否则将是非常混乱。