I'm 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:
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 href="http://social.msdn.microsoft.com/Forums/en/async/thread/163ef755-ff7b-4ea5-b226-bbe8ef5f4796"
rel="noreferrer">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.List`1.ForEach(Action`1 action)
at
MyApplication.CustomControls.Controls.MyCustomControl.d__3b.MoveNext()
in C:\Documents and
Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line
625
at
System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.b__1(Object
state)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate
callback, Object args, Int32 numArgs)
at
MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method,
Object args, Int32 numArgs, Delegate catchHandler)
at
System.Windows.Threading.DispatcherOperation.InvokeImpl()
at
System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object
state)
at System.Threading.ExecutionContext.runTryCode(Object
userData)
at
System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode
code, CleanupCode backoutCode, Object userData)
at
System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext,
ContextCallback callback, Object state)
at
System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback
callback, Object state, Boolean ignoreSyncCtx)
at
System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback
callback, Object state)
at
System.Windows.Threading.DispatcherOperation.Invoke()
at
System.Windows.Threading.Dispatcher.ProcessQueue()
at
System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam,
IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr
hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at
MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at
System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object
args, Int32 numArgs)
at
MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method,
Object args, Int32 numArgs, Delegate catchHandler)
at
System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan
timeout, Delegate method, Object args, Int32 numArgs)
at
MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr
lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG&
msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame
frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame
frame)
at System.Windows.Threading.Dispatcher.Run()
at
System.Windows.Application.RunDispatcher(Object ignore)
at
System.Windows.Application.RunInternal(Window window)
at
System.Windows.Application.Run(Window window)
at
System.Windows.Application.Run()
at MyApplication.App.Main() in C:\Documents
and Settings\...\MyApplication\obj\Debug\App.g.cs:line 50
at
System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity,
String[] args)
at
Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at
System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at
System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback
callback, Object state, Boolean ignoreSyncCtx)
at
System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback
callback, Object state)
at
System.Threading.ThreadHelper.ThreadStart()
Answer
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>(() =>
GetCustomers());
public
static class AsyncHelpers
{
///
/// Execute's an async Task method which has a void
return value synchronously
///
/// name="task">Task method to execute
public static
void RunSync(Func 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);
}
///
/// Execute's an async
Task method which has a T return type synchronously
///
/// Return
Type
/// Task method to
execute
///
public
static T RunSync(Func> 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> items =
new
Queue>();
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 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;
}
}
}
No comments:
Post a Comment