Greg, On 28 March 2015 at 15:17, Greg Keogh <[email protected]> wrote: > Folks, I have an existing library with lots of traditional non-async methods > in it, and I want to provide async versions of the old methods. Would you > consider this to be a simple and trustworthy way of getting this done? > > public Thing GetThing(int key) > { > // This is the existing method > return ... > } > > public Task<Thing> GetThingAsync(int key) > { > return Task.Run(() => > { > return GetThing(key); > } > } > > So I just make matching pairs where the old methods are just wrapped in > Task.Run(...). It seems too easy. This is Framework 4.5, and I have a vague > recall that Task.Run doesn't work this easily in 4.0 and it's a bit more > verbose.
On 12 April 2015 at 07:30, Tristan Reeves <[email protected]> wrote: > Also to obtain any *actual* benefit from an async counterpart the > implementation more than likely has to change. For example, a sync method > might use the synchronous File API, whereas an async counterpart can gain > benefit by using the async File API. Sorry for resurrecting an old thread, but I recently did some async work and I thought of this question. You are right. Wrapping synchronous methods in Task.Run is "too easy". The problem is I/O. One important thing I learned about async/await is that it works best when all the I/O in your stack is asynchronous, as Tristan mentioned. If you perform synchronous, blocking I/O in the delegate passed to Task.Run, a pool thread will likely end up blocked, waiting on I/O. This is a bad thing. In highly concurrent workloads, blocked pool threads can lead to scalability issues and eventually resource starvation of the thread pool (see Jeffrey Richter's book, CLR via C#, chapter 28 "I/O-Bound Asynchronous Operations"). I have also observed the runtime limiting the rate at which it enlarges the pool. Basically, one should never wait in a pool thread. Unfortunately this means you probably shouldn't implement GetThingAsync with GetThing. And unfortunately this also means the asynchronous pattern "infects" all methods "down" the call stack. That is, all the methods transitively called by GetThingAsync should use asynchronous I/O and never wait. See the section "Async All the Way" in this[1] MSDN magazine article. Corollary: You should also favour lock-free data structures and continuations over blocking synchronisation primitives when synchronising tasks running on pool threads. This means avoiding the "lock" keyword, Mutexes, AutoResetEvents and other WaitHandles as these can all put a pool thread to sleep. [1] https://msdn.microsoft.com/en-us/magazine/jj991977.aspx -- Thomas Koster
