18

I just came across a strange behavior with using async methods in structures. Can somebody explain why this is happening and most importantly if there is a workaround? Here is a simple test structure just for the sake of a demonstration of the problem

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

Now, let's use the structure and do the following calls

var sInstance = new Structure(25);
sInstance.Change(35);
await sInstance.ChangeAsync(45);

The first line instantiates the structure and the sInstance._Value value is 25. The second line updates the sInstance._Value value and it becomes 35. Now the third line does not do anything but I would expect it to update the sInstance._Value value to 45 however the sInstance._Value stays 35. Why? Is there a way to write an async method for a structure and change a structure field's value?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
alex.49.98
  • 609
  • 5
  • 13
  • 9
    Workaround: don't use mutable structs to start with. Basically, I suspect your struct is getting boxed for callbacks to occur, and that the mutation *is* happening, but within that box. But really, don't use mutable structs. This is an interesting *new* (to me) variation on how to get bitten by them, but there are *plenty* of gotchas like this. – Jon Skeet Jul 26 '15 at 22:25
  • 1
    Interesting and strongly related to [How to deal with side effects produced by async/await when it comes to mutable value types?](http://stackoverflow.com/questions/24811287/). – Jeppe Stig Nielsen Jul 26 '15 at 22:29
  • I wanted to point out that making the structure immutable is not an option in my case. The whole system coded long ago and we just have to extend certain part with async methods. Rewriting the whole thing does not look feasible at this point but I perfectly understand the concept of having structures to be immutable. Is there a cheaper workaround? – alex.49.98 Jul 26 '15 at 22:30
  • 1
    This is probably a variation of this: http://stackoverflow.com/questions/29230626/why-is-enumerator-movenext-not-working-as-i-expect-it-when-used-with-using-and-a/29230956#29230956 - If it is, then Microsoft has confirmed that the enumerator/movenext problem was a bug in the compiler and they fixed this in Roslyn. It would thus be interesting to see if this problem still existed if compiled in Visual Studio 2015. – Lasse V. Karlsen Jul 26 '15 at 22:32
  • Can you simply convert it to a class instead of a struct? – Blorgbeard Jul 26 '15 at 22:37
  • Nope, tested it in Visual Studio 2015 now, with .NET 4.6, same problem exists. – Lasse V. Karlsen Jul 26 '15 at 22:39
  • But it is the same kind of reason. `this` is lifted out into an object and this is modified, the original isn't. So yes, as @JonSkeet points out, *don't use mutable structs*. – Lasse V. Karlsen Jul 26 '15 at 22:42
  • I tried many ways. I tried using ContinueWith in the struct method but nothing helped. It seems that the only way to make it work is to change the struct to class however it reduce the code efficiency because some of our structs are really should be structs having single inner field. – alex.49.98 Jul 27 '15 at 00:19
  • 1
    A single inner *mutable* field - Microsoft (among others) would still recommend they be classes: [Choosing between Class and Struct](https://msdn.microsoft.com/en-us/library/ms229017.aspx) – Blorgbeard Jul 27 '15 at 01:06

1 Answers1

19

Why?

Because of the way your struct is lifted onto the state machine.

This is what ChangeAsync actually looks like:

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

The important line is this:

<ChangeAsync>d__.<>4__this = this;

The compiler lifts a copy of your struct into its state-machine, effectively updating its copy with the value 45. When the async method completes, it has mutated the copy, while the instance of your struct remains the same.

This is somewhat an expected behavior when dealing with mutable structs. That's why they tend to be evil.

How do you get around this? As I don't see this behavior changing, you'll have to create a class instead of a struct.

Edit:

Posted this as an issue on GitHub. Received a well educated reply from @AlexShvedov, which explains a bit deeper the complexity of structs and state machines:

Since execution of every closure can be arbitrarily delayed, we need some way to also delay the lifetime of all the members captured into closure. There is no way to do it in general for this of value type, since value type can be allocated on stack (local variables of value types) and stack space will be reused on method execution exit.

In theory, when value type is stored as a field of some managed object/element of array, C# can emit closure code to do struct mutation inplace. Unfortunately, there is no knowledge on where this value is located when emitting struct member code, so C# decided simply to force users handle this situation manually (by copying the this value most of the time, as error message suggested).

Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321