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