1

I've seen a lot of questions about this area (e.g. https://stackoverflow.com/a/54413147/1756750 , or https://stackoverflow.com/a/55139219/1756750 ), but unfortunately I haven't found anything in official documentation ( https://github.com/dotnet/docs/issues/11360 ).

Let's consider the following example.

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);
    }
}

The method HandlePerson() creates an empty person and calls the method ReadPerson(person), which somehow gets a person. After that the HandlePerson() uses this object somehow. Such code pattern code be used, if we want to reuse object, or arrays.

Depends on implementation details of ReadPerson(person) (e.g., (3) vs (4)), this method could be executed on the same thread (which was used for HandlePerson()), or it can be rescheduled to another thread.

The next observation is both Person.name and Person.nameLength have atomic read/write (e.g. What operations are atomic in C#? ). However, if I am not mistaken, it doesn't mean that we don't need volatile by default, because we still can see old state (null value) in general case (not specific for async/await).

I also tried to check Jit ASM for this code. I can see 2 different lock cmpxchg [ecx], edi, which could be a memory barriers, which were automatically created by the compiler, because of async/await. However, it could be something not-related to this. Also, C# compiler could optimize some memory barriers, because we use x86, which has pretty strict memory guaranties (compare to ARM64).

So, my question is, do we need to use volatile for mutable fields (e.g. Person.name, see (1) vs (2)), and if yes in what conditions?

Denis
  • 3,595
  • 12
  • 52
  • 86
  • 1
    Related: [Are you there, asynchronously written value?](https://stackoverflow.com/questions/28871878/are-you-there-asynchronously-written-value) – Theodor Zoulias Apr 08 '23 at 15:03

1 Answers1

3

I've seen a lot of questions about this area (e.g. https://stackoverflow.com/a/54413147/1756750 , or https://stackoverflow.com/a/55139219/1756750 ), but unfortunately I haven't found anything in official documentation ( https://github.com/dotnet/docs/issues/11360 ).

I'm a little confused. You say that you've found SO answers for this, but no official docs. So... you ask on SO? Even if you get an answer, it's still an SO answer and not official docs.

do we need to use volatile for mutable fields

No. There are sufficient memory barriers that you don't need volatile (or your own memory barriers), even if the code after await resumes on a different thread.

AFAIK, this is not actually documented anywhere officially, but consider a semi-proof-by-contradiction: if this wasn't the case, then the vast, vast majority of async code would be wrong, including lots of BCL and MS framework code. If it was that easy to write wrong async code, then there would be lots of documentation around it, articles complaining about pitfalls, etc. But none of that exists, and it is highly likely that the async code is correct.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Hello Stephen, thanks for the response. Yea, It looks silly that I am asking it on SO, even though I was thinking about official documentation :) I like your semi-proof-by-contradiction, and still wonder, why we don't have official statements about it. It is a bit stressful for me, when I need to write such code, but there is no official proof, that it is correct. Anyway, it is just my personal problem. – Denis Apr 08 '23 at 15:05