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.