Friday 19 January 2018

c# - How would I run an async Task method synchronously?

itemprop="text">


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());



Code is from href="http://social.msdn.microsoft.com/Forums/en/async/thread/163ef755-ff7b-4ea5-b226-bbe8ef5f4796"
rel="noreferrer">here



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

php - file_get_contents shows unexpected output while reading a file

I want to output an inline jpg image as a base64 encoded string, however when I do this : $contents = file_get_contents($filename); print &q...