首页 > 解决方案 > 从异步方法设置时 AsyncLocal 的值不正确

问题描述

AsyncLocal当从类的异步方法设置时,为什么不保留字段的值。考虑这个例子:

var scope = new TestScope();

// The default value is 0
Console.WriteLine(scope.Counter.Value);

// Setting the vlaue to 2
await scope.SetValueAsync();

Console.WriteLine(scope.Counter.Value);

class TestScope
{
    public readonly AsyncLocal<int> Counter = new AsyncLocal<int> { Value = 0 };

    public async Task SetValueAsync()
    {
        this.Counter.Value = 2;

        await Task.Yield();
    }
}

预期的输出应该是:

0

2

但实际情况是:

0

0

为什么退出SetValueAsync方法时会更改异步上下文?

标签: c#.net.net-coreasync-await

解决方案


这是因为 AsyncLocal 具有写时复制语义。基本上,当从内部异步范围更改时,会生成值的副本并且不会修改原始对象。

您可以通过将值包装在引用对象中来解决此问题。那是因为即使你复制了一个引用,你最终仍然在操作同一个对象:


class TestScope
{
    public readonly AsyncLocal<StrongBox<int>> Counter = new AsyncLocal<StrongBox<int>> { Value = new StrongBox<int>(0) };

    public int Value
    {
        get => Counter.Value.Value;
        set => Counter.Value.Value = value;
    }

    public async Task SetValueAsync()
    {
        this.Value = 2;

        await Task.Yield();
    }
}

static async Task Test()
{
    var scope = new TestScope();

    // The default value is 0
    Console.WriteLine(scope.Value);

    // Setting the value to 2
    await scope.SetValueAsync();

    Console.WriteLine(scope.Value);
}

StrongBox<T>只是将值类型包装在引用类型中的一种便捷方式。但是任何其他引用类型都可以做到这一点。


推荐阅读