none
Pitfalls of Async? RRS feed

  • Question

  • I started to look into on simple examples how Async works and I find it extremely misleading that all tutorials and articles just talk about how great it is and almost none talk about the pitfalls (like with all hypes). I am not really interested how it works, but more interested in what situations it has to be avoided.

    Let me draw an example. Assume I have an async method that I use as an event handler for a button. It's doing something timely, so I'm using async and await in the event handler code. The advantage is that as soon as wait is reached, the event handler will actually return, so the UI code can continue. Fine.

    But, that also means that when the code fragment after the await actually continues in the event handler, then that code has to look around to see, what has changed since it got suspended. Why? During the suspension time, anything could change in the program. The button might have been long gone, maybe a completely different page is shown already, so continuing with the event handler code would even make no sense at all.

    How is this situation is supposed to be handled?

    Another example could be: how about if multiple requests are coming in, faster than the event handler can handle, so multiple event handler instances are called, each starting a new async branch. But what if this makes no sense from the program point of view and only one request should be kept (e.g. the last one)? How do you keep track how many branches you already "forked"?

    If I have to keep track all ongoing async branches so that they can be cancelled, then I barely see the point of using async at all. It just makes you believe it's for async programming when it's really not. Async programming is much broader and includes also handling of all these issues. So how do you handle these things above?


    • Edited by mchalls Friday, December 21, 2012 5:42 PM
    Friday, December 21, 2012 5:40 PM

Answers

  • Hi! Those are great questions. I'm on the team that introduced async, and I've spent the past year looking out for common pitfalls.

    * Read the Parallel Programming team blog,  http://blogs.msdn.com/b/pfxteam/
    Stephen Toub has been writing lots there about how to use await properly, and what things to avoid.

    * I did a short channel9 series "Three essential tips for async" http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async
    The biggest pitfall I've seen is people writing void-returning async methods where Task-returning would be better. Another pitfall is people misunderstanding CPU-bound vs IO-bound.

    * You remark that "the code has to see what changed since it got suspended". Yes this is true. I haven't seen people run into problems relating to this much in practice. I think it's because they either disable their buttons at the start of the button-click handler (to avoid re-entrancy), or because they're doing localized-enough stuff within the body of the method that they're not greatly affected. The chief thing that can't be localized to within the body is cancellation, and that's easy enough to watch for through cancellation tokens - all you need do is pass cancellation tokens down to each method.

    * Some people in this thread suggested semaphores or other multithreading primitives. I think that's the wrong approach. One of the joys of async is that pre-emption is less granular than it was with multithreading.

    * You talk about how to throttle it when requests come in too fast. Async gives good idioms for throttling. For instance, just the simple loop "while (await x.MoveNextAsync()) Process(x.Current)" will take items from "x" as fast as they can be processed, but no faster. The task then is how to write something like "x". The Dataflow library provides types that can be used here.

    * You ask about how to keep track of ongoing async branches so they can be cancelled. In general you don't need to do this just to cancel them. That's because cancellation is done by passing a cancellation token when you kick off a branch. Later, you do "c.Cancel()" so that everyone hooked up to that token will get cancelled.

    * In general, it's true, I do tend to keep track of the async branches I kick off. That way, when my UI page closes, I can await until all of those branches have cleanly finished. Keeping track of them isn't hard: e.g. "m_loadTask = LoadPictureAsync();". Then in response to a close request I can do "await Task.WhenAll(m_loadTask, ...)".

    • Proposed as answer by Lucian Wischik, MSFT Wednesday, May 15, 2013 8:53 PM
    • Marked as answer by mchalls Thursday, May 16, 2013 5:21 PM
    Wednesday, May 15, 2013 8:53 PM

All replies

  • You must use semiphores with event handler to synchronize the Asyn Event with the rest of the applications.  Event handlers don't allow a 2nd event handler of the same type to occur until the first is released so yo uwon't have any forking in the same thread.  An evvent handler must indicate to the rest of the application when data is received (or tranmit buffer is empty). 

    Read the
    Wiki Article on semiphores.

    http://en.wikipedia.org/wiki/Semaphore_(programming)


    jdweng

    Friday, December 21, 2012 6:34 PM
  • I've done a great deal of async type design over the years - not just on Windows either.

    I've not looked in-depth at the "await" support in the latest C# yet but do know that it is just a "shorthand" for what is ordinary asyn callbacks and contexts, that is it is really a powerful abstraction supported by the C# compiler to making async look much simpler.

    Until I do explore it in depth I can't say too much but I do wonder if it is a bit simplistic because it forces the post-await processing to be predefined as the sequence of statments in the method doing the await.

    I much prefer a state machine based design where the completion that causes an async callback calls a method based upon a state and passes a context object around.

    So rather than limiting the code to do what is coded after the "await" the system can use a state variable that is used to select a callback method.

    This allows the state to be alterable dynamically so that the specific callback is not restricted to some block of code following "await" but depends wholly upon that state.

    I suppose one could create a method that uses await in a loop with a switch statement and cases for various states, perhaps with each case calling a method - but whether this is still simpler to follow than a more formal event/state async pattern I can't say offhand, I suspect its actually messier because a 2D array of delegates indexed by state-id and event-id is much more flexible and generic.

    I've created high performance and highly scalable message based servers using this design pattern and had great success with it, so my view is the await pattern is useful for some scenarios and much simpler but is not ideal for a range of other scenarios.

    I'm quite prepared to be corrected on this if my understanding of await is incorrect - so do please feel free to diasgree!

    Cap'n




    • Edited by Captain Kernel Friday, December 21, 2012 6:54 PM
    • Marked as answer by mchalls Monday, December 24, 2012 9:03 AM
    • Unmarked as answer by mchalls Thursday, May 16, 2013 5:23 PM
    Friday, December 21, 2012 6:45 PM
  • "You must use semiphores with event handler to synchronize the Asyn Event with the rest of the applications."

    I'm aware of the sync solutions, but that does not change the fact that you still have to take care about these things explicitly, just like with threads or any other form of parallelism. So arguing that async should be used instead of explicit handling of sync issues seems to be completely misleading, which was my original concern.

    "Event handlers don't allow a 2nd event handler of the same type to occur until the first is released so yo uwon't have any forking in the same thread"

    I'm not sure I understand this one completely. For one thing, how an event handler knows that a called async function is "done" in a sense that there is no any ongoing branch that is still waiting? Even if it can, this pattern is still valid if we are talking about pure function calls (i.e. not a UI event handler). That means the programmer has to keep track how many instances were already called. Which leads back to the question: so what's the gain really.

    Maybe it's my fault that I believed all the articles and expected too much from Async. Not that I expected all complexity of parallelism to magically disappear, but it seems to me it's more like a neat trick than a real and fundamental help in parallel programming.

    Friday, December 21, 2012 6:50 PM
  • That sounds interesting.

    I personally enjoyed message passing systems and the actor model way better then any threaded code or async solution, because at least that one does not lie, it's extremely simple and puts all real problems right into your face, meaning you actually solve the real problem (parallelism) instead of trying to solve all kind of weird way of working things (like threads, shared data, mutexes, etc). Erlang seemed to capture some really imporant essence (immutability and actor model), because in almost all cases violating those two causes nothing but headache.

    But it's true, even in actor models the state is in the code structure itself (in most languages), however there are a few languages allowing dynamic change of handling code. TTCN-3 for example with its altsteps, too bad that it is a very underused language.

    Friday, December 21, 2012 6:59 PM
  • With any communication protocol you need an application level command/response level protocol.  The Async method need to either know the number of bytres being sent or a terminating chartacter at the end of the receive data.

    jdweng

    Friday, December 21, 2012 7:16 PM
  • The main pitfalls with the new async support, IMO, are the same pitfalls with asynchrony in general.  Yes, you still have to verify your state and make sure that the work that's being performed "later" is appropriate at that point.  That doesn't change.

    All the language support does is dramatically simplify the code you have to write to handle the mechanics of asynchrony.  This is especially true when you're trying to synchronize across multiple asynchronous operations, and also want to synchronize within a proper SynchronizationContext.  Many seemingly "simple" asynchronous method calls can make code that's incredibly difficult to write correctly normally, but async/await simplifies this so that it's fairly easy to at least see what's going on, and write the code correctly.  You still need to make sure your state is correct, but having the compiler write the state machine for you to get the operational plumbing correct is quite valuable.


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Friday, December 21, 2012 7:52 PM
    Moderator
  • Hi,

    there are always pitfalls while programming. Almost always it is up to you to avoid them.

    You have to decide if it make sense using async event handler for a button or not. If it is a long calculation that fills some objects and the result of that you can see on some page, why not using it? If some other programming logic depends on it, it might not be a good idea to use it.

    Multiple requests can be handled on several ways. When using only one request, why using the last one? If that is the case, you have to track them, as you said. If using all in order of appearence (for example writing a file), you have to lock it with a Semaphore.

    Maybe this could give you some new ideas of and whether using async. By the way it is not only about using async, it is rather about using Threading.

    Best regards

    Chris

    Friday, December 21, 2012 9:04 PM
  • After several iterations I think I have to admit, I expected too much from Async. It's nothing more than a neat trick to avoid callback nightmares (and it fits there very nicely). However it is still full with scheduling issues (e.g. check after all timely operation whether it makes sense to actually continue - which kinda defeats advantage of having the linear code; or keep in mind all the weird threading issues - which is not the async's fault).

    I also admit I'm more a fan for actor model and already see how much easier it would be handling all these kind of issues with that. A bitter pill to swallow :(

    Nevertheless, I'm still curious about any kind of "gotcha" stories related to async. I had a few (from the fact I expected too much, i.e. not understood completely), perhaps others have too.


    • Edited by mchalls Monday, December 24, 2012 8:58 AM
    Monday, December 24, 2012 8:58 AM
  • I found another confusing aspect of using async and the linear code. Assume you use multiple await calls, then the code is completely linear, which sounds as a good thing for the first sight, however can be confusing that you still have to check the entire global state around the code (since anything could have changed around your code when it's running).

    So you end up having big blocks of code that actually runs in different states, but now they are in one linear block. This goes from simple things like refreshing the local variables, to greater extents. In short I'm a bit skeptic about the true benefit of async. It is very nice just executing things you know you have to; but when you need to handle multiple events not just the await ones, then I find the linear style actually more disturbing.

    Thursday, December 27, 2012 10:10 AM
  • I think you need to read a good book on "Multithreading" and "State processing".  I got my degree in Electrical Engineering over 30 years ago and what you described were taught in many of my classes.  There are many engineering decisions that are made as part of programming.  What you descibe are not pitfalls, but instead engineering tradeoffs.  It is part of good programming practices.  A poorly written program will not work properly.  When you program usng "Mutliprocessing" and/or "Asynchronous Processing" you must build a state machine to properly step through your code and you must use semiphores to synchronize you processes.

    jdweng

    Thursday, December 27, 2012 10:25 AM
  • I think you and many other commenters are misunderstanding me completely. I am aware of these things. Parallel execution and threads are not new to me. Async was, and I started learning it watching Channel 9 videos and tutorials. My first impression was that the hype was quite big, in many cases it was suggested to use this no matter what.

    However when actually started using it, I realized that it is not always adequate. The goal of my question was to collect the experiences from others regarding when they found it inadequate. Hence my comment above too.

    Thursday, December 27, 2012 10:31 AM
  • You kind of hit a sour point of mine.  Over the years I have had to fix a lot of synchronization problems in software.  I was a hardware Designer and whenever there were issues the software designers would always blame the hardware first.  I have a BSEE in Computer Science and a MSCS (Master in Computer Science.  When I went to fix problems I always looked at the problems from both sides of the fence.  Most of the times I was able to fix the problem by modifying the software.  I have the skills and background to be able to determine when a problem was with the hardware and when a problem was with the software.

    jdweng

    Thursday, December 27, 2012 10:37 AM
  • Wut. How hardware is coming now into the picture? Maybe you meant posting it into another thread? :-)

    Anyway, I really just wanted to understand the whole picture from the language and parallel programming point of view. I know way too many examples when some ideas were overused (half of the industry is like that) and I just wanted to make sure I learn about where others found the boundaries of async.

    I admit it may seem I question a lot of "trivial" things - but that's exactly what I wanted to do. Just look into some async tutorials and you will see, it always talks about the greatness and never about the proper place.

    (I also still have the feeling that Microsoft is going into one direction even if they are not completely aware of it. Many of these "fixes" and features in compiler/language are pointing directly to message passed and immutable systems even though I know they are not targeting those. But it's only my opinion and I don't want the thread to go to that direction.)

    Thursday, December 27, 2012 10:51 AM
  • async/await has nothing to do with threads, and almost everything to do with flattening out a world of event-driven code where logic is scattered everywhere. You could have only one single thread and still be getting tons more done with multiple interweaving awaiting sequences taking place. It all depends how deep into the rabbit hole you're willing to explore.

    They tout it so much to beginners though because it finally tames Overlapped I/O without the confusing semantics. For advanced users it's the second coming, and the possibilities are endless.

    Imagine if WPF or WinForms not only had "ShowDialog()" but also "await ShowDialogAsync()", where you could run a modal dialog without having to run a nested message pump.


    • Edited by electricninja Thursday, January 3, 2013 12:04 PM grammar
    Thursday, January 3, 2013 12:03 PM
  • Hi! Those are great questions. I'm on the team that introduced async, and I've spent the past year looking out for common pitfalls.

    * Read the Parallel Programming team blog,  http://blogs.msdn.com/b/pfxteam/
    Stephen Toub has been writing lots there about how to use await properly, and what things to avoid.

    * I did a short channel9 series "Three essential tips for async" http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async
    The biggest pitfall I've seen is people writing void-returning async methods where Task-returning would be better. Another pitfall is people misunderstanding CPU-bound vs IO-bound.

    * You remark that "the code has to see what changed since it got suspended". Yes this is true. I haven't seen people run into problems relating to this much in practice. I think it's because they either disable their buttons at the start of the button-click handler (to avoid re-entrancy), or because they're doing localized-enough stuff within the body of the method that they're not greatly affected. The chief thing that can't be localized to within the body is cancellation, and that's easy enough to watch for through cancellation tokens - all you need do is pass cancellation tokens down to each method.

    * Some people in this thread suggested semaphores or other multithreading primitives. I think that's the wrong approach. One of the joys of async is that pre-emption is less granular than it was with multithreading.

    * You talk about how to throttle it when requests come in too fast. Async gives good idioms for throttling. For instance, just the simple loop "while (await x.MoveNextAsync()) Process(x.Current)" will take items from "x" as fast as they can be processed, but no faster. The task then is how to write something like "x". The Dataflow library provides types that can be used here.

    * You ask about how to keep track of ongoing async branches so they can be cancelled. In general you don't need to do this just to cancel them. That's because cancellation is done by passing a cancellation token when you kick off a branch. Later, you do "c.Cancel()" so that everyone hooked up to that token will get cancelled.

    * In general, it's true, I do tend to keep track of the async branches I kick off. That way, when my UI page closes, I can await until all of those branches have cleanly finished. Keeping track of them isn't hard: e.g. "m_loadTask = LoadPictureAsync();". Then in response to a close request I can do "await Task.WhenAll(m_loadTask, ...)".

    • Proposed as answer by Lucian Wischik, MSFT Wednesday, May 15, 2013 8:53 PM
    • Marked as answer by mchalls Thursday, May 16, 2013 5:21 PM
    Wednesday, May 15, 2013 8:53 PM