Here's a workaround I found that works for all cases (including suspended dispatchers). It's not my code and I'm still working to fully understand it, but it does work.
It can be called using:
customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());
Code is from here
public static class AsyncHelpers
{
/// <summary>
/// Execute's an async Task<T> method which has a void return value synchronously
/// </summary>
/// <param name="task">Task<T> method to execute</param>
public static void RunSync(Func<Task> task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
synch.Post(async _ =>
{
try
{
await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
}
/// <summary>
/// Execute's an async Task<T> method which has a T return type synchronously
/// </summary>
/// <typeparam name="T">Return Type</typeparam>
/// <param name="task">Task<T> method to execute</param>
/// <returns></returns>
public static T RunSync<T>(Func<Task<T>> task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
T ret = default(T);
synch.Post(async _ =>
{
try
{
ret = await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
return ret;
}
private class ExclusiveSynchronizationContext : SynchronizationContext
{
private bool done;
public Exception InnerException { get; set; }
readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
readonly Queue<Tuple<SendOrPostCallback, object>> items =
new Queue<Tuple<SendOrPostCallback, object>>();
public override void Send(SendOrPostCallback d, object state)
{
throw new NotSupportedException("We cannot send to our same thread");
}
public override void Post(SendOrPostCallback d, object state)
{
lock (items)
{
items.Enqueue(Tuple.Create(d, state));
}
workItemsWaiting.Set();
}
public void EndMessageLoop()
{
Post(_ => done = true, null);
}
public void BeginMessageLoop()
{
while (!done)
{
Tuple<SendOrPostCallback, object> task = null;
lock (items)
{
if (items.Count > 0)
{
task = items.Dequeue();
}
}
if (task != null)
{
task.Item1(task.Item2);
if (InnerException != null) // the method threw an exeption
{
throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
}
}
else
{
workItemsWaiting.WaitOne();
}
}
}
public override SynchronizationContext CreateCopy()
{
return this;
}
}
}
Asynchronous programming does "grow" through the code base. It has been compared to a zombie virus. The best solution is to allow it to grow, but sometimes that's not possible.
I have written a few types in my Nito.AsyncEx library for dealing with a partially-asynchronous code base. There's no solution that works in every situation, though.
Solution A
If you have a simple asynchronous method that doesn't need to synchronize back to its context, then you can use Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
You do not want to use Task.Wait
or Task.Result
because they wrap exceptions in AggregateException
.
This solution is only appropriate if MyAsyncMethod
does not synchronize back to its context. In other words, every await
in MyAsyncMethod
should end with ConfigureAwait(false)
. This means it can't update any UI elements or access the ASP.NET request context.
Solution B
If MyAsyncMethod
does need to synchronize back to its context, then you may be able to use AsyncContext.RunTask
to provide a nested context:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
*Update 4/14/2014: In more recent versions of the library the API is as follows:
var result = AsyncContext.Run(MyAsyncMethod);
(It's OK to use Task.Result
in this example because RunTask
will propagate Task
exceptions).
The reason you may need AsyncContext.RunTask
instead of Task.WaitAndUnwrapException
is because of a rather subtle deadlock possibility that happens on WinForms/WPF/SL/ASP.NET:
- A synchronous method calls an async method, obtaining a
Task
.
- The synchronous method does a blocking wait on the
Task
.
- The
async
method uses await
without ConfigureAwait
.
- The
Task
cannot complete in this situation because it only completes when the async
method is finished; the async
method cannot complete because it is attempting to schedule its continuation to the SynchronizationContext
, and WinForms/WPF/SL/ASP.NET will not allow the continuation to run because the synchronous method is already running in that context.
This is one reason why it's a good idea to use ConfigureAwait(false)
within every async
method as much as possible.
Solution C
AsyncContext.RunTask
won't work in every scenario. For example, if the async
method awaits something that requires a UI event to complete, then you'll deadlock even with the nested context. In that case, you could start the async
method on the thread pool:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
However, this solution requires a MyAsyncMethod
that will work in the thread pool context. So it can't update UI elements or access the ASP.NET request context. And in that case, you may as well add ConfigureAwait(false)
to its await
statements, and use solution A.
Update, 2019-05-01: The current "least-worst practices" are in an MSDN article here.
Best Answer
This is a known bug in the beta. To quote Stephen Toub: