patterncsharpCritical
How would I run an async Task<T> method synchronously?
Viewed 0 times
runhowasyncmethodwouldsynchronouslytask
Problem
I am learning about async/await, and ran into a situation where I need to call an async method synchronously. How can I do that?
Async method:
Normal usage:
I've tried using the following:
I also tried a suggestion from here, however it doesn't work when the dispatcher is in a suspended state.
Here is the exception and stack trace from calling
System.InvalidOperationException
Message: RunSynchronously may not be called on a task unbound to a delegate.
InnerException: null
Source: mscorlib
StackTrace:
``
at MyApplication.Custom
Async method:
public async Task GetCustomers()
{
return await Service.GetCustomersAsync();
}Normal usage:
public async void GetCustomers()
{
customerList = await GetCustomers();
}I've tried using the following:
Task task = GetCustomers();
task.Wait()
Task task = GetCustomers();
task.RunSynchronously();
Task task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)I also tried a suggestion from here, however it doesn't work when the dispatcher is in a suspended state.
public static void WaitWithPumping(this Task task)
{
if (task == null) throw new ArgumentNullException(“task”);
var nestedFrame = new DispatcherFrame();
task.ContinueWith(_ => nestedFrame.Continue = false);
Dispatcher.PushFrame(nestedFrame);
task.Wait();
}Here is the exception and stack trace from calling
RunSynchronously:System.InvalidOperationException
Message: RunSynchronously may not be called on a task unbound to a delegate.
InnerException: null
Source: mscorlib
StackTrace:
``
at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
at System.Threading.Tasks.Task.RunSynchronously()
at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
at MyApplication.CustomControls.Controls.MyCustomControl.b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
at System.Collections.Generic.List1.ForEach(Action`1 action)at MyApplication.Custom
Solution
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:
Code is from here
It can be called using:
customerList = AsyncHelpers.RunSync>(() => GetCustomers());Code is from here
public static class AsyncHelpers
{
///
/// Synchronously execute's an async Task method which has a void return value.
///
/// The Task method to execute.
public static void RunSync(Func task)
{
var oldContext = SynchronizationContext.Current;
var syncContext = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncContext);
syncContext.Post(async _ =>
{
try
{
await task();
}
catch (Exception e)
{
syncContext.InnerException = e;
throw;
}
finally
{
syncContext.EndMessageLoop();
}
}, null);
syncContext.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
}
///
/// Synchronously execute's an async Task method which has a T return type.
///
/// Return Type
/// The Task method to execute.
/// The result of awaiting the given Task.
public static T RunSync(Func> task)
{
var oldContext = SynchronizationContext.Current;
var syncContext = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncContext);
T result;
syncContext.Post(async _ =>
{
try
{
result = await task();
}
catch (Exception e)
{
syncContext.InnerException = e;
throw;
}
finally
{
syncContext.EndMessageLoop();
}
}, null);
syncContext.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
return result;
}
private class ExclusiveSynchronizationContext : SynchronizationContext
{
private readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
private readonly Queue> items =
new Queue>();
private bool done;
public Exception InnerException { get; set; }
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 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;
}
}
}Code Snippets
public static class AsyncHelpers
{
/// <summary>
/// Synchronously execute's an async Task method which has a void return value.
/// </summary>
/// <param name="task">The Task method to execute.</param>
public static void RunSync(Func<Task> task)
{
var oldContext = SynchronizationContext.Current;
var syncContext = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncContext);
syncContext.Post(async _ =>
{
try
{
await task();
}
catch (Exception e)
{
syncContext.InnerException = e;
throw;
}
finally
{
syncContext.EndMessageLoop();
}
}, null);
syncContext.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
}
/// <summary>
/// Synchronously execute's an async Task<T> method which has a T return type.
/// </summary>
/// <typeparam name="T">Return Type</typeparam>
/// <param name="task">The Task<T> method to execute.</param>
/// <returns>The result of awaiting the given Task<T>.</returns>
public static T RunSync<T>(Func<Task<T>> task)
{
var oldContext = SynchronizationContext.Current;
var syncContext = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncContext);
T result;
syncContext.Post(async _ =>
{
try
{
result = await task();
}
catch (Exception e)
{
syncContext.InnerException = e;
throw;
}
finally
{
syncContext.EndMessageLoop();
}
}, null);
syncContext.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
return result;
}
private class ExclusiveSynchronizationContext : SynchronizationContext
{
private readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
private readonly Queue<Tuple<SendOrPostCallback, object>> items =
new Queue<Tuple<SendOrPostCallback, object>>();
private bool done;
public Exception InnerException { get; set; }
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);
}
Context
Stack Overflow Q#5095183, score: 548
Revisions (0)
No revisions yet.