Friday 3 November 2017

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...