Compose, compose, compose. Reuse, reuse, reuse.
In the last post on cancellable “awaiting” on .NET events, I was so busy focusing on the wrapping the click event in order to get a smooth async control flow for the caller, I neglected to do the same inside the click event wrapper code.
The framework team already provided an extension method (WithCancellation) to make any task cancellable. By using WithCancellation(), we can do away with the Continuation call and just use a simple try/finally block to guarantee that the event handler gets unsubscribed regardless of how the task finishes.
1: public static async Task<RoutedEventArgs> WhenClicked(this Button element, CancellationToken token)
3: TaskCompletionSource<RoutedEventArgs> tsc = new TaskCompletionSource<RoutedEventArgs>();
4: RoutedEventHandler routedEventHandler = null;
6: // set the event handler to complete the task
7: routedEventHandler = (Object s, RoutedEventArgs e) => tsc.SetResult(e);
9: // hook up to the event
10: element.Click += routedEventHandler;
13: return await tsc.Task.WithCancellation(token);
17: // always unhook the event handler after the task is finished
18: element.Click -= routedEventHandler;
FOR BONUS POINTS: Why do we have to make this method async? Why not just return a task instead and not use await on line 13.
So you can unhook the event.
is it because you need the finally code to happen after the task has completed?
Yes, the event will get unhooked either way. The finally block guarantees that. But if you were to just return the task, then the finally block would execute immediately and unhook the event. Then the task would never complete. With the async/await in place, the code below it (the finally block) wont execute until the task is complete.