2013-04-18

UI Guidelines for Async

So far, a lot of attention has been paid on how to use async, but there is little guidance on when to push operations off the UI thread.

I've collected a couple of quotes here that are applicable. They're not much, but they're all we have in the form of official guidance.

The 50ms Rule

Operations should be made asynchronous if they "could take longer than 50 milliseconds to execute" (Jason Olson, Keeping apps fast and fluid with asynchrony in the Windows Runtime).

This is the rule that Microsoft followed with the WinRT APIs; anything taking less than 50ms is considered "fast" and close enough to "immediate" that they do not require an asynchronous API. I also recommend this rule be applied to any synchronous blocks of code; if they are likely to take more than 50ms, push them onto the thread pool via Task.Run.

The Hundred Continuations Per Second Rule

It's usually a good idea to use ConfigureAwait(false) in your library code, and only run continuations on the UI thread if they actually need a UI context. But how many continuations can the UI thread really be expected to handle?

At least on the WinRT platform, "the guidance is that just a hundred or so awaits resuming on the UI thread per second will be fine, but a thousand per second will be bad" (Lucian Wischik, Async Design Patterns). Naturally, this is assuming that the continuations do not block for any substantial period of time.

Caveats

It's difficult to lay down any kind of firm guidance. It's easy to have a situation where the 4-core 8-GB developer's machine handles a thousand continuations on the UI thread easily, but that old XP laptop your client has isn't going to hack it. With WinRT, the hardware is at least somewhat constrained, so the above rules have a stronger meaning on that platform. In the real world, you'll have to test with realistic clients and derive your own numbers.

4 comments:

  1. Hi Stephen -

    I often enjoy your posts, thanks!

    I have a question related to this topic ... I know that in WPF 4.5 we can indirectly update the UI by making changes to data-bound objects, and even databound collections.

    I assume something at some point gets marshaled to the UI thread, and this may be different depending on if INotify[...] is used or a DependencyProperty?

    My real question this is: when configuring an await, if the remaining tasks are just changing databound objects ... should I prefer ConfigureAwait(false) or no? Technically i don't need to resume on the UI thread due to the feature just described, but maybe I'd rather if the databinding will eventually lead to a thread swap or something anyway?

    I may be wrong on a lot of my understanding, but thanks for any clarification here!

    ReplyDelete
    Replies
    1. I should have clarified ... the indirect updating I mentioned was that you can update the UI from non-UI threads through databinding... in .NET 4.5 this is even possible for databound collections which I believe is new

      Delete
    2. Good questions, Travis. I don't have all the answers, but I'll give you my opinion. :)

      First off, any kind of property or collection changed event does need to be raised on the UI thread, so you'll need to marshal to that thread one way or another (either explicitly or implicitly).

      I don't *think* dependency properties will marshal back to the UI thread automatically, and INotify* will only do so if you implement it as such. Some people do implement automatic marshaling to the UI thread. I have never done this; instead, I treat any data-bound properties as though they were a part of the UI (e.g., in async methods, I do *not* use ConfigureAwait(false) if I need to update a data-bound property).

      The new cross-thread collection synchronization in .NET 4.5 is not fully automatic; it's more of a way to coordinate ObservableCollection (or other collection) updates between the UI thread and background thread(s). I have not used it yet, but I would consider it if performance analysis indicated it would help. This is assuming you actually need a background thread accessing it (e.g., Task.Run); if you just have async methods, you may as well just marshal back to the UI yourself and not mess with the cross-thread collection synchronization, IMO.

      There's always been *some* support for cross-thread UI operations, but I just pretend they don't exist. For one thing, I try to write portable code, and it's always hard to keep straight which little features are on Windows Store vs. Windows Phone vs. Silverlight vs. WPF. Cross-thread UI operations are one more thing I don't have to worry about if I don't use them.

      Delete
  2. Sound advice, thanks! The async/await mindset is yielding major improvements in code readability and performance for me, and I'm sure everyone else. It really feels like the "right way" to do asynchrony... especially since it separates the concepts of asynchrony and multi-threading. Some of the TPL code I was so proud of a few years ago feels like an embarrassment in my codebase now, hah... not all of it, TPL is great when used for what it was designed for.

    ReplyDelete