Some Motivations
If you have programmed Windows Phone or Windows Store apps, you may know about Dispatcher in Windows Phone or CoreDispatcher in Windows Store. In this article I will focus more on Windows Phone implementation, but basically this should be very similar in Windows Store.
In general, those dispatchers are ways to marshal UI interactions from a background context (any context that is not the UI context) to the UI. Trying to directly access (both read and write) any UI control or any view model property that has been bound to a UI control from a background context will trigger an UnauthorizedAccessException. Instead you will have to call Dispatcher.BeginInvoke(Action action) method (or Deployment.Current.Dispatcher.BeginInvoke(Action action) if you are not in a page) and put all the UI accessing codes in the action parameter.
It work perfectly fine until I tried to introduce some async logic inside.
You could do it simply this way:
1 2 3 4 |
Dispatcher.BeginInvoke(async () => { await myTask(); }); |
It could compile and run without crashing the app, but there are 2 caveats that most folks familiar with async/await programming would recognize immediately: the async void action will silence exception inside (I mean the thread will just die without being handled properly, and all the code inside the action will run in parallel with the code after the BeginInvoke. The former could be fixed by wrapping a try catch block around the content of the async Action, and the latter would not be a problem if you don’t care about synchronizing the UI context and the calling background context.
However, the latter has become a problem to me when I tried to marshal some async codes to the UI context, but I wanted the background context to continue only when the UI logic had been finished. I was amazed by the design of async/await in C# once again because it helped to synchronize different contexts so easily. Basically I just had to put a TaskCompletionSource inside the Action, and called SetResult when the action was finishing, and awaited that source after the BeginInvoke call. The code looked like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var tcs = new TaskCompletionSource<bool>(); Dispatcher.BeginInvoke(async () => { try { await myTask(); tcs.SetResult(true); } catch (TaskCanceledException) { tcs.SetCanceled(); } catch (OperationCanceledException) { tcs.SetCanceled(); } catch (Exception ex) { tcs.SetException(ex); } } await tcs.Task; |
It is very much the same as converting a event-based implementation into an async/await implementation. You can synchronize, can know if the UI logic is canceled or has any exception. You can even transfer the result from the UI context back to background context by changing the generic type of the TaskCompletionSource and the parameter in the SetResult call.
The real problem
It came when I tried to all knowledge into a separate package to reuse into the future. My goals is to simply call one short function and have all the logistics (including the context and exception synchronization) done for me. It should be something like this:
1 |
await ThreadHelper.RunInUIContextAsync(someAsyncTaskHere); |
And the implementation of the function will look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public static Task RunInUIContextAsync(someAsyncTaskHere) { var tcs = new TaskCompletionSource<bool>(); Deployment.Current.Dispatcher.BeginInvoke(async () => { try { await MyTaskAsync(); tcs.SetResult(true); } catch (TaskCanceledException) { tcs.SetCanceled(); } catch (OperationCanceledException) { tcs.SetCanceled(); } catch (Exception ex) { tcs.SetException(ex); } }; return tcs.Task; } |
MyTaskAsync would be passed into the function by the parameter someAsyncTaskHere part, so I the key here is to choose someAsyncTaskHere. Parameter type of Action was obviously not going to work here because we would not be able to await it inside the function. So I choose Task (or IAsyncAction if you like) for the implementation.
The interesting lesson
Here come the most interesting part! I have watched many people create a task first and then await it later, so a “good” implementation looked so obvious to me:
1 2 3 4 5 6 |
public static Task RunInUIContextAsync(Task myTask) { ... await myTask(); ... } |
And the function call would be:
1 2 |
var task = MyTaskAsync(); await ThreadHelper.RunInUIContextAsync(task); |
Remember that MyTaskAsync is a asynchronous function that has some manipulating on the UI, so it has to be called in the UI context. I expected no exception will be thrown as I already marshaled the call to the UI context, but an UnauthorizedAccessException still happened. Debugging with some breakpoints showed that the function MyTaskAsync start running even before RunInUIContextAsync is called, and some other test confirm that the function actually ran right after I create the Task instance named task, not after I use await inside RunInUIContextAsync function. And the task instance was created in a background context, hence an exception was thrown.
The solution
So the easiest way to solve this is to pass the whole function as a parameter instead of a task object. This could be done by changing the function prototype into:
1 |
public static Task RunInUIContextAsync(Func<Task> myTaskFunction) |
This is use in this way:
1 |
await ThreadHelper.RunInUIContextAsync(MyTaskAsync); |
Func<Task> means the inputting function cannot have any parameter, but it can be easily workaround this way:
1 |
await ThreadHelper.RunInUIContextAsync(new Func<Task>(() => return MyTaskAsync(param1, param2);)); |
Conclusion
I feel good to learn a little bit more about how the async/await work, and I hope that this article would help you if you need to implement some wrapping function involving calling or marshaling async Tasks.
If you have a better solution for my ThreadHelper, please just let me know in the comment below!