I have a WPF application running a background task which uses async/await
. The task is updating the app's status UI as it progresses. During the process, if a certain condition has been met, I am required to show a modal window to make the user aware of such event, and then continue processing, now also updating the status UI of that modal window.
This is a sketch version of what I am trying to achieve:
async Task AsyncWork(int n, CancellationToken token)
{
// prepare the modal UI window
var modalUI = new Window();
modalUI.Width = 300; modalUI.Height = 200;
modalUI.Content = new TextBox();
using (var client = new HttpClient())
{
// main loop
for (var i = 0; i < n; i++)
{
token.ThrowIfCancellationRequested();
// do the next step of async process
var data = await client.GetStringAsync("http://www.bing.com/search?q=item" + i);
// update the main window status
var info = "#" + i + ", size: " + data.Length + Environment.NewLine;
((TextBox)this.Content).AppendText(info);
// show the modal UI if the data size is more than 42000 bytes (for example)
if (data.Length < 42000)
{
if (!modalUI.IsVisible)
{
// show the modal UI window
modalUI.ShowDialog();
// I want to continue while the modal UI is still visible
}
}
// update modal window status, if visible
if (modalUI.IsVisible)
((TextBox)modalUI.Content).AppendText(info);
}
}
}
The problem with modalUI.ShowDialog()
is that it is a blocking call, so the processing stops until the dialog is closed. It would not be a problem if the window was modeless, but it has to be modal, as dictated by the project requirements.
Is there a way to get around this with async/await
?
This can be achieved by executing
modalUI.ShowDialog()
asynchronously (upon a future iteration of the UI thread's message loop). The following implementation ofShowDialogAsync
does that by usingTaskCompletionSource
(EAP task pattern) andSynchronizationContext.Post
.Such execution workflow might be a bit tricky to understand, because your asynchronous task is now spread across two separate WPF message loops: the main thread's one and the new nested one (started by
ShowDialog
). IMO, that's perfectly fine, we're just taking advantage of theasync/await
state machine provided by C# compiler.Although, when your task comes to the end while the modal window is still open, you probably want to wait for user to close it. That's what
CloseDialogAsync
does below. Also, you probably should account for the case when user closes the dialog in the middle of the task (AFAIK, a WPF window can't be reused for multipleShowDialog
calls).The following code works for me:
[EDITED] Below is a slightly different approach which uses
Task.Factory.StartNew
to invokemodalUI.ShowDialog()
asynchronously. The returnedTask
can be awaited later to make sure the user has closed the modal dialog.As a completely different approach, have a look at David Wheelers concept application for a interesting variation. http://coloringinguy.com/2012/11/07/model-view-viewmodel-sample/
Essentially he has a semi transparent overlay that he moves to the front of a control that's taking a long time to update. I think is a cool User experience and worthy of reviewing as it create that modal experience without blocking UI updates.