Skip to content

Instantly share code, notes, and snippets.

@nick-beer
Created May 30, 2021 10:50
Show Gist options
  • Save nick-beer/0ed387322b8f44183520428f19292564 to your computer and use it in GitHub Desktop.
Save nick-beer/0ed387322b8f44183520428f19292564 to your computer and use it in GitHub Desktop.
Using PushFrame in a WPF application to prevent UI thread deadlock while synchronously waiting for a task.
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private void SomeSynchronousEntryPoint()
{
bool ranOnUI;
var deadlock = false;
if (deadlock)
{
// The following will always deadlock - not really any way around it...
ranOnUI = DoBackgroundWorkThenUIWorkAsync(Dispatcher, CancellationToken.None).Result;
}
else
{
var task = DoBackgroundWorkThenUIWorkAsync(Dispatcher, CancellationToken.None);
ranOnUI = PushFrameUntilComplete(Dispatcher, task, CancellationToken.None);
}
Debug.Assert(ranOnUI);
}
private async Task<bool> DoBackgroundWorkThenUIWorkAsync(Dispatcher dispatcher, CancellationToken token)
{
return await Task.Run(async () =>
{
var ranOnUI = false;
await Task.Yield();
await dispatcher.BeginInvoke(() =>
{
token.ThrowIfCancellationRequested();
ranOnUI = dispatcher.CheckAccess();
});
return ranOnUI;
});
}
private static T PushFrameUntilComplete<T>(Dispatcher dispatcher, Task<T> task, CancellationToken token)
{
// For us, the exitWhenRequested parameter here is important. The default is 'true', which means
// that if the shutdown has been requested for the dispatcher, this frame will exit. By passing
// this paramter, we get explicit control over the 'frame.Continue' property, allowing us to pump
// messages (and thus process UI Activities) after shutdown has been requested for our dispatcher.
var frame = new DispatcherFrame(exitWhenRequested: false);
using (token.Register(() => frame.Continue = false))
{
var op = dispatcher.InvokeAsync(
async () =>
{
try
{
return await task;
}
finally
{
frame.Continue = false;
}
});
Dispatcher.PushFrame(frame);
var asyncTask = op.Task.Unwrap();
return asyncTask.IsCompletedSuccessfully
? asyncTask.GetAwaiter().GetResult()
: throw new OperationCanceledException();
}
}
}
}
@nick-beer
Copy link
Author

nick-beer commented May 30, 2021

Not intended to be a complete/runnable example - only to demonstrate a concept.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment