- Developers won't anticipate that accessing a task's Result property can introduce a deadlock.
- Developers won't anticipate race conditions due to a concurrent SynchronizationContext.
The problem from the first example actually does happen quite often. There are plenty of stackoverflow questions and blog posts about it. IMO there should be compiler warnings whenever you use Task.Result or Task.Wait. Actually I would prefer compile errors instead of just warnings, and for the implementations of Result and Wait to just be `throw new WHAT_IS_WRONG_WITH_YOU_DO_NOT_BLOCK_ON_TASKS_AAARGH_EXCEPTION()`, but that might be a bit harsh.
The problem from the second example is not really an issue with await/async as opposed to a deeper language issue. Any concurrency mechanism in C# will be vulnerable to the fact that you can define and access shared mutable variables (including the solution proposed in the post).
I'm sure I just dont understand it, but hearing about leaky abstractions like he describes in the article just makes me that much more determined to never touch them. Too much magic, expertly hidden inside a closed .net runtime.
Putting cool technology in the hands of blue collar developers.
I think it's great if you follow standard design patterns but it could be a real source of problems for inexperienced programmers once a race condition emerges.
It feels to me like async/await is the malloc of concurrent programming, and coroutines are the garbage collection of concurrent programming. You hand in a little performance in exchange for a lot less complexity. Go has shown you can also do this without only-immutable data.
Am I missing something?
Without segmented stacks you have to conserve threads, which basically leads to Task and async/await.
Edit: Almost forgot a important part: your COM thread is hugely important to how Windows UI pumping works. Async guarantees what thread you get to run on, where coroutines often just say, "you get the thread that you get."
This is not entirely true - the code as written does not have a race condition because the two accesses are run sequentially. Accessing the same variable by two separate threads with no synchronization primitives is actually okay if those two threads never run in parallel. Now, if you called many ReadTask()'s in a row and you had thread_pool > 1 (as in the GUI/ASP.NET application), then you would have a race condition. But if you're accessing a shared variable from a multithreaded context that should be somewhat obvious. It would depend on the programmer's understanding of the async/await paradigm, which I think is the author's point. ;)
Assuming you just want to propagate exceptions, you have to mess with TaskContinuationSource (I.e, promises) and be damn certain you have wired it into all the right places.
Async/await makes error handling much more uniform and "obviously correct" than stringing callbacks together.
while (await reader.ReadAsync(cts)) { ... }The author states two threads accessing a single resource is a race-condition, but that's only true when the threads are competing for that resource - in the case outlined in the article, it would be sequential, and only one thread would be accessing the resource at one time. No race condition.
I think it's not too wise to think of async/await as a special coroutine which never uses multiple threads, but as an easy way to write synchronous code and have it execute asyncronously. If you need fine control over what threads are used, maybe you're better off controlling it manually.
Async/await is an abstraction on top of tasks, where you write code with do-while loops and try-catch and it gets translated into GCD-esque continuation-using code. It lets you write the asynchronous code in an imperative style, so it looks more like the rest of the code you write in C#.