我是否需要在 C# 中使用 volatile 来使用 async/await 来处理可变对象字段?

问题描述 投票:0回答:1

我看到很多关于这方面的问题(例如https://stackoverflow.com/a/54413147/1756750,或https://stackoverflow.com/a/55139219/1756750),但不幸的是我没有在官方文档中找到任何内容(https://github.com/dotnet/docs/issues/11360)。

让我们考虑以下示例。

public class Person
{
    // public byte[] name;        // (1)
    public volatile byte[] name;  // (2)

    public int nameLength;
}

public class PersonService
{
    private async Task ReadPerson(Person person)
    {
        // byte[] personBytes = File.ReadAllBytes("personPath.txt");          // (3)
        byte[] personBytes = await File.ReadAllBytesAsync("personPath.txt");  // (4)
        person.name = personBytes;
        person.nameLength = personBytes.Length;
    }

    public async Task HandlePerson()
    {
        Person person = new Person();
        await ReadPerson(person);
        
        string personName = System.Text.Encoding.UTF8.GetString(person.name, 0, person.nameLength);
        Console.WriteLine(personName);
    }
}

方法

HandlePerson()
创建一个空的
person
并调用方法
ReadPerson(person)
,它以某种方式得到一个
person
。在那之后,
HandlePerson()
以某种方式使用了这个对象。如果我们想重用对象或数组,可以使用此类代码模式代码。

取决于

ReadPerson(person)
的实现细节(例如,
(3)
vs
(4)
),这个方法可以在同一个线程上执行(用于
HandlePerson()
),或者它可以被重新安排到另一个线程。

下一个观察是

Person.name
Person.nameLength
都具有原子读/写(例如C#中哪些操作是原子的?)。但是,如果我没记错的话,这并不意味着我们默认不需要
volatile
,因为在一般情况下我们仍然可以看到旧状态(
null value
)(不特定于异步/等待)。

我还尝试检查 Jit ASM 以获取此代码。我可以看到 2 个不同的

lock cmpxchg [ecx], edi
,这可能是内存障碍,由编译器自动创建,因为
async/await
。但是,它可能与此无关。此外,C# 编译器可以优化一些内存屏障,因为我们使用
x86
,它具有非常严格的内存保证(与 ARM64 相比)。

所以,我的问题是,我们是否需要对可变字段使用

volatile
(例如
Person.name
,参见
(1)
vs
(2)
),如果需要,在什么条件下?

c# multithreading async-await roslyn volatile
1个回答
0
投票

我看到很多关于这方面的问题(例如https://stackoverflow.com/a/54413147/1756750,或https://stackoverflow.com/a/55139219/1756750),但不幸的是我没有在官方文档中找到任何内容(https://github.com/dotnet/docs/issues/11360)。

我有点困惑。你说你已经找到了答案,但没有官方文档。所以......你问SO?即使你得到了答案,它仍然是一个 SO 答案而不是官方文档。

我们是否需要对可变字段使用 volatile

没有。有足够的内存屏障,您不需要

volatile
(或您自己的内存屏障),即使
await
之后的代码在不同的线程上恢复。

AFAIK,这实际上并没有正式记录在任何地方,但考虑一个半矛盾的证明:如果这 不是 情况,那么绝大多数

async
代码将是错误的,包括很多BCL 和 MS 框架代码。如果写错
async
代码那么容易,那么就会有很多关于它的文档、抱怨陷阱的文章等等。但这些都不存在,
async
代码很可能是正确的。

© www.soinside.com 2019 - 2024. All rights reserved.