It is quite common that in our application we use Modal Windows. Now more and more the asynchronous programming model is used pervasively. C# and VB.Net languages even provides a syntactic sugar for them using the async/await feature.
It is desirable that one could expect to await
the ShowDialog
unfortunately, ShowDialog
is a synchronous API. By definition, synchronous API can’t be await
ed; It doesn’t makes sense either trying to await a synchronous method.
However, you can make the synchronous method “awaitable” by wrapping it in a Task
— you do it using Task.Run
or Task.Factory.StartNew
. That executes the given method asynchronously in ThreadPool thread making a synchronous operation asynchronous.
Sounds great right, So it is just a matter of wrapping ShowDialog
call in Task.Run
to make it asynchronous. Let’s do that.
Your synchronous code below
Window window = YourWindow(); | |
bool? result = window.ShowDialog(); |
Becomes this
Window window = YourWindow(); | |
bool? result = await Task.Run(()=> window.ShowDialog()); |
Alright, We’re done right? Yes, except that it won’t work –we’re done with it.
This could work for any other synchronous method, but not for anything which is related to UI. UI elements typically have “Thread affinity”. Which means they have to be dealt with the thread which it was created. Otherwise, You’ll get an InvalidOperationException
well known as “cross thread exception”.
In this case we could move the creation of window inside the Task.Run
(with some trick) and fix the violation of thread affinity requirement. But that will not yield you what you would expect. Window will not be shown as Modal window of the owner. etc.
It turns out that we can’t show the UI in worker thread(at least gracefully). It is evident that we must call the ShowDialog method from the user interface thread only. But the method being synchronous, preventing us from awaiting it. Are we back to the place where we started? No way around it?
Well, we can satisfy the requirement of calling the method in user interface thread itself and asynchronously also. Thanks to Dispatcher.BeginInvoke which makes it possible.
That is a different API which isn’t awaitable; nevertheless msft gifted us the TaskCompletionSource<T> type which could wrap anything in Task.
With just two more lines of code, it is possible
TaskCompletionSource<bool?> completion = new TaskCompletionSource<bool?>(); | |
window.Dispatcher.BeginInvoke(new Action(() => completion.SetResult(window.ShowDialog()))); | |
bool? result = await completion.Task; |
We could make this code as a reusable piece of code. Maybe a nice extension method as below
using System; | |
using System.Threading.Tasks; | |
using System.Windows; | |
namespace AsyncShowDialog | |
{ | |
public static class AsyncWindowExtension | |
{ | |
public static Task<bool?> ShowDialogAsync(this Window self) | |
{ | |
if (self == null) throw new ArgumentNullException("self"); | |
TaskCompletionSource<bool?> completion = new TaskCompletionSource<bool?>(); | |
self.Dispatcher.BeginInvoke(new Action(() => completion.SetResult(self.ShowDialog()))); | |
return completion.Task; | |
} | |
} | |
} |
Then you could call it as
Window window = YourWindow(); | |
bool? result = await window.ShowDialogAsync(); |
Note that the code is about Wpf, but it doesn’t matter much. You can pretty much do the same in any UI technology with very little change. For example, in winforms, you’d use Form
instead of Window
, DialogResult
instead of bool?
and Control.BeginInvoke
method instead of Dispatcher.BeginInvoke
.
That’s about it. Hope you enjoyed the post. Let’s make everything asynchronous :). Have fun.