1

I have several async methods that need to synchronize back to the main ui thread.

async Task MyAsyncMethod() {
    await DoSomeThingAsync().ConfigureAwait(true);
    DoSomethingInTheGui();
}

now i need to call them from a syncronous event handler that is triggered from the gui thread, and the event handler cannot complete until the async method is done. so MyAsyncMethod().Wait() is not an option, neither is some kind of fire-and-forget solution.

This approach using Nito.AsyncEx seemed promising, but it still deadlocks: https://stackoverflow.com/a/9343733/249456

The only solution i've found seems like a hack to me:

public void RunInGui(Func<Task> action)
{
    var window = new Window();
    window.Loaded += (sender, args) => action()
       .ContinueWith(p => {
            window.Dispatcher.Invoke(() =>
            {
                window.Close();
            });
        });
        window.ShowDialog(); 
}

Is there a way to get the same effect (block the calling method, allow syncronization back to the gui thread) without creating a new window?

Note: I am aware that refactoring would probably be the best option, but that is a massive undertaking we have to do over longer time. Also worth mentioning that this is a plugin for Autodesk Inventor. The api has several quirks, and all API calls (even non-ui related) have to be executed from the main/ui thread.

Also worth mentioning is that we keep a reference to the main threads dispatcher and use MainThreadDispatcher.InvokeAsync(() => ... ) all around the codebase.

Community
  • 1
  • 1
Henrik
  • 11
  • 3
  • 4
    You need to not block your UI thread. So long as you have a requirement to block your UI thread (particularly while other processes actually need to use the UI thread) then you're going to be in a world of hurt. Remove that requirement if you value your sanity. – Servy Mar 22 '17 at 15:46
  • *Why* use a synchronous event handler? Why not an *asynchronous& event handler? Eg `async void Button_Click(..){ await MyAsyncMethod1(); await MyAsyncMethod2();}` – Panagiotis Kanavos Mar 22 '17 at 15:48
  • Another option is to use Progress to send messages back to the UI thread. The payload of a Progress doesn't have to be a string or a progress message, it could be any object. Check [Async in 4.5: Enabling Progress and Cancellation in Async APIs](https://blogs.msdn.microsoft.com/dotnet/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis/) – Panagiotis Kanavos Mar 22 '17 at 15:50
  • The problem is that the event handlers are not "mine". This is a plugin for Autodesk Inventor. I cannot use async void because the event handlers have out parameters: `void Eventhandler(..., out EventhandlingEnum handled)` and i need to make sure that the inventor host application does not continue before i my methods have completed – Henrik Mar 22 '17 at 16:30

1 Answers1

8

The only solution i've found seems like a hack to me

All sync-over-async solutions are hacks. I enumerate the most common ones in my article on brownfield async. It's important to note that no solution exists for arbitrary code. Every solution either deadlocks or throws in some situation.

One of the easier hacks is to block on a thread pool thread:

Task.Run(() => action()).Wait();

However, if your action() requires a UI context, then that won't work.

The next step is to attempt using a single-threaded context. This is the approach taken by my AsyncContext type:

AsyncContext.Run(() => action());

This would deadlock if action is waiting for the UI thread's message queue to be pumped. Since this deadlocks but Window works, this appears to be the case.

Dropping further down a level, you could use nested pumping. Please note that nested pumping opens up a whole world of hurt due to unexpected reentrancy. There's some code on GitHub that shows how to do nested pumping on WPF. However, I highly recommend that you refactor your code rather than embrace reentrancy.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks for the quick reply. Refactoring is my preferred solution, but thats a massive undertaking, so it will have to be done gradually. How can i identify where my code is waithing for the UI threads message queue to be pumped? – Henrik Mar 22 '17 at 17:02
  • @Henrik: You could use `AsyncContext` and then when it deadlocks take a look at the call stack on your UI thread. – Stephen Cleary Mar 22 '17 at 17:33