I was playing with async / await when I came across the following:
class C
{
    private static string str;
    private static async Task<int> FooAsync()
    {
        str += "2";
        await Task.Delay(100);
        str += "4";
        return 5;
    }
    private static void Main(string[] args)
    {
        str = "1";
        var t = FooAsync();
        str += "3";
        str += t.Result; // Line X
        Console.WriteLine(str);
    }
}
I expected the result to be "12345", but it was "1235". Somehow '4' was eaten up.
If I split Line X into:
int i = t.Result;
str += i;
Then the expected "12345" results.
Why is it so? (Using VS2012)
Why is it so? (Using VS2012)
You're running this in a console application, which means there is no current synchronization context.
As such, the portion of the FooAsync() method after the await runs in a separate thread.  When you do str += t.Result you're effectively making a race condition between the += 4 call, and the += t.Result.  This is because string += is not an atomic operation.
If you were to run the same code within a Windows Forms or WPF application, the synchronization context would be captured and used for the += "4", which means this would all run on the same thread, and you wouldn't see this issue.
C# statements of the form x += y; are expanded to x = x + y; at compile time.
str += t.Result; becomes str = str + t.Result;, where str is read before getting t.Result. At this point in time, str is "123". When the continuation in FooAsync runs, it modifies str and then returns 5. So str is now "1234". But then the value of str that was read before the continuation in FooAsync ran (which is "123") is concatenated with 5 to assign to str the value "1235".
When you break it into two statements, int i = t.Result; str += i;, this behavior can't happen.
This is a race condition. You're not properly synchronizing access to the shared variable between the two threads of execution that you have.
Your code is probably doing something like this:
Then we get to the interesting line:
str += t.Result;
Here it's broken up into several smaller operations.  It will first get the current value of str.  At this point in time the async method (in all probability) hasn't finished yet, so it'll be "123".  It then waits for the task to complete (because Result forces a blocking wait) and adds the result of the task, in this case 5 to the end of the string.
The async callback will have grabbed and re-set str after the main thread had already grabbed the current value of str, and then it will overwrite str without it ever being read as the main thread is soon to overwrite it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With