Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Tip 3: Wrap events up in Task-returning APIs and await them

Download

Right click “Save as…”

Async Tip #3: You can wrap events up in Task-returning APIs and await them. This can dramatically simplify code.

Slides and source code are available on Lucian's blog.

This video introduces a new pattern for dealing with events: you can await them! Now why would you want to do that? Well, in some cases like responding to a button-click, it's fine to handle events in the way that you always have with top-level event handlers. But if you're building a complicated UI that has to orchestrate a whole load of events (StoryboardCompleted, MediaElementCompleted, PointerMoved, PointerReleased, ...) then it makes for cleaner code to await them. The key is the type TaskCompletionSource introduced in .NET4.

Tags:

Follow the Discussion

  • I think this is an important new pattern and want more people to start using it. Here are some more resources:

     

  • This set of screen casts has been interesting, but this is the first one I've got serious problems with. At a conceptual level, I think you're abusing Task. Events typically are expected to be fired more than once. The only real exception to this is an event that's part of the EAP pattern. You're StoryBoard example is an EAP scenario, and there I have no problems with replacing that pattern with the TAP pattern. In fact, the EAP pattern should be considered harmful at this point, and TAP should be used for all new code and all legacy async code should be wrapped. All of you other examples, though, aren't part of the EAP, and as such are expected to be raised multiple times. For this, Task is the wrong solution. IObservable is the correct solution, and Rx observables are awaitable.

    At an implementation level, you also have problems. Your TaskCompletionSource pattern fails to handle exceptions.

  • Hi wkempf! You say "IObservable is the correct solution." That might be true, but it's by no means obvious, and requires supporting evidence. That supporting evidence would be in the form of example problems, to see which approach winds up simpler, more maintanable, and more debuggable.

    I'd like to see examples! How about you try writing the apple-game into RX and post here?

    Here's another example. I picked "shake detection" since that's been a common RX example. Let's compare them side by side:

    // Shake detection using async (from a stream of Accelerometer.ReadingChangedEventArgs)
    
    public async void StartShakeWatcher(Accelerometer accel) {
        var t = DateTime.Now;
        while (true) {
            var e = await accel.ReadingChangedAsync();
            if (Math.Pow(e.AccelerationX, 2) + Math.Pow(e.AccelerationY, 2) < 1.1) continue;
            if ((DateTime.Now - t).Milliseconds < 200) Shake(this, null);
            t = DateTime.Now;
        }
    }
    
    
    // Shake detection using RX (from a stream of Accelerometer.ReadingChangedEventArgs)
    
    public static IObservable<Event<AccelerometerArgs>> GetShakeObserver(Accelerometer accel) {
        return accel.ReadingChangedObservable()
               .Where(e => (Math.Pow(e.EventArgs.Reading.AccelerationX,2) +
                            Math.Pow(e.EventArgs.Reading.AccelerationY,2) > 1.1))
               .TimeInterval()
               .Where(k => k.Interval.Milliseconds < 200)
               .Select(k => k.Value);
    }

    And here's how you'd consume the two:

    Private Sub OnShake() Handles ShakeWatcher.Shake
        ' ...
    End Sub
    
    Overrides Sub OnNavigatedTo()
        GetShakeObserver().Subscribe(Sub() ...)
    End Sub
    

    I think this one's a wash. Same number of lines of code. The async one allows single-step debugging and doesn't involve lambdas. The RX one is marginally more efficient.

     

    In cases where you have streams of events and need to apply advanced combinators to them, especially when combing streams in complex ways, then RX is unquestionably the best solution. But where you're just doing orchestration of a UI app like here? then it's not so clear.

     

  • Let's change the name in your example to just WhenReadingChanged so we can use the same name for both the Task solution and the IObservable solution. Now realize that an IObservable is an awaitable and you're first example works whether WhenReadingChanged returns a Task or an IObservable. Returning an IObservable is more appropriate, however, because the event is recurring. IObservable can handle composition of recurring event streams, while Task cannot. So returning a Task limits usage, while returning IObservable does not, and both provide the same usability in your scenarios where you're using a Task.

    // Shake detection using async (from a stream of Accelerometer.ReadingChangedEventArgs)
    public async void StartShakeWatcher(Accelerometer accel) {
        var t = DateTime.Now;
        while (true) {
            // Doesn't matter if WhenReadingChanged returns a Task or an IObservable, this works the same.
            var e = await accel.WhenReadingChanged();
            if (Math.Pow(e.AccelerationX, 2) + Math.Pow(e.AccelerationY, 2) < 1.1) continue;
            if ((DateTime.Now - t).Milliseconds < 200) Shake(this, null);
            t = DateTime.Now;
        }
    }
    

  • BTW, your StartShakeWatcher example violates one of the tenets from earlier in this series. Async void methods and delegates should only be used for top level event handlers. One of the reasons for this is to allow the API to be composable, but once you take that into consideration your original IObservable implementation is probably the better implementation. *shrug* Regardless, I believe my point still stands and streams of events should be modeled at the API level with IObservable and not Task.

  • I'm not sure where "API" is a question? We're already given an event-based API from the accelerometer - I reckon that events will be with us for a long time. The question is (1) whether Shake should be exposed as an event or an IObservable, (2) whether we should implement our logic using RX combinators or language combinators.

  • You can't change the framework. By API, I mean *your* API. In this case, the WhenReadingChanged (or name of your choice) extension method. You still seem to be missing the crux of this, though. "(2) whether we should implement our logic using RX combinators or language combinators" sure seems to be missing my point. By returning an IObservable instead of a Task I have not limited you to Rx combinators. You can still use async/await to compose. What I've done is made the API (WhenReadingChanged) usable by both language and Rx combinators.

    The guidelines as I see how they should be followed:

    1. If the legacy event is part of an EAP implementation, wrap it with a Task, otherwise wrap it with an IObservable.

    2. When composing, if you're composing streams you'll use Rx combinators, otherwise you should prefer language combinators using async/await.

    (2) is a little oversimplified, so there's likely exceptions to be found, but 1 seems pretty obvious and I see no room for variation.

  • In the RX sample, where I wrote "accel.ReadingChangedObservable()", it was actually just a call to Observable.FromEventPattern(accel, ...). I omitted the lambdas to keep the code simpler.

    So what you're talking about as an "API" would merely be the call to Observable.FromEventPattern. And what you're saying is that I could have used "Observable.FromEventPattern" identically in both the async version and the RX version. Is that right?

  • When I say API I'm not interested in how it's implemented, so all of the talk about FromEventPattern is static. What I'm talking about is the public API, in this case the signature of WhenReadingChanged (again, I'm renaming so the name doesn't give an indication of IObservable or Task). The question is whether WhenReadingChanged should return a Task or an IObservable. I'm maintaining it should return an IObservable.

    You are, however, correct when you ask if my point is that ReadingChangedObservable could have been used identically in both the async version and the Rx version. That's precisely the point. By defining your API (ReadingChangedObservable / ReadingChangedAsync / WhenReadingChanged / whatever) to return an IObservable you can use it with either Rx composition (appropriate when composing streams) or with async/await (appropriate when composing the "next" event as was done in several examples here). Contrast this with returning a Task. If you do that, you've lost the stream and can only compose with async/await (actually, we're totally glossing over composing with ContinueWith... the key is that await works not on Task but on awaitables, and both Task and IObservable are awaitables). IMHO, nothing is gained by returning Task, but a lot is lost, so you simply shouldn't return Task here.

  • Bent Rasmussenexoteric stuck in a loop, for a while

    This pattern looks cool. I'm going to have a close look at this when doing the next C# project! Thanks for the tip!

  • Aaron StainbackAceHack AceHack

    @wkempf: I agree with you on your opinion of IObservable vs Task.  Tasks are great in their place but if IObservable is the right decision because of a reoccurring event then you loose a lot of composition options if you return a Task instead.  Too bad I can't give you a plus one Smiley

    @ljw1004: I also think these async tips have been very informative and would love to see more on the subject in a more in depth fashion.

  • I whole-heartily agree with @wekempf on the point of IObservable vs Task. Task has a one-off "I did this, here's the result" concept, and IOberservable has an "events over time" concept.

    The while(true){} is a code smell, and it indicates that there's missing code. Mainly, the ability to stop listening to the "event stream". If I wanted it to stop, I'd have to kill the thread it's running on.

    IObservable also gives us more composition options through the use of Reactive Extensions. Moreover, it's cross-language compatible, and doesn't use any compiler-specific features on an "API".

    Lets talk about the return types of the two functions illustrated. The async example's return type is void. Meaning that it isn't functional, and the entire call is definitely a side-effect. It also means if I wanted to re-use this shake-detection code in more than one place, I'd either be injecting a callback into the method (remember when you suggested we didn't have to use a lambda in the async version?), or I'd have to write two entirely separate methods with different side effects. Both of those are no-nos.

    The IObservable, on the other hand, simply composes the events in a functional manner without creating any side effects, and gives the consumer the ability to either compose the events further, or add multiple explicit, properly-placed side effects without injecting a lambda, or copying code.

    While the async stuff has it's uses, complex event handling is not one of them.

    Interesting, thought-provoking example. Thanks!

  • Erik PorterHuman​Compiler Now with more apps
    Hey all who don't think Task wrapped events is the best way to do it, how about you do the same example but with IObservable so we can compare real code? Smiley
  • Actually, Lucian did a pretty good job writing the Rx implementation of this code, but I'll go back and rewrite both to show how to *use* them. BBL.

  • ElliotElliot

    Thankyou for these videos. I had been having trouble concentrating, but Lucian's helpful videos and personability have really helped me get back into things.

  • It is an interesting pattern, and I don't have the experience to participate in the above debate. Yet.

    But I would add one point that quite often seems lost in discussions about what direction the language and framework should take: KISS.  If it's hard to read and understand for most developers, it's probably not a great idea.  If it's hard to explain, it's probably not a good idea.  I would like to emphasize that I'm not criticizing Lucian or taking sides in this debate in any way.  Just asking that the KISS principle be weighed in when deciding which approach for this or anything is "best"

    I have quite often had to deal with code that has evolved over years.  On occasion, I even wrote the original, and had to come back later and try to recall what the original thinking was.  Most people don't care and don't want to care how easy it will be to maintain their code over time. They like to think "it's not my problem".

  • Hi Lucian,
    I'm just back from watching this video. Great information but what I don't get is: why do you wrap the code into a try/finally block?

  • Oops forget it. I had a bad day. *ashamed*

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.