我正在学习异步/等待,遇到了需要同步调用异步方法的情况。我该怎么做呢?
异步方法:
public async Task<Customers> GetCustomers()
{
return await Service.GetCustomersAsync();
}
正常的用法:
public async void GetCustomers()
{
customerList = await GetCustomers();
}
我尝试使用以下方法:
Task<Customer> task = GetCustomers();
task.Wait()
Task<Customer> task = GetCustomers();
task.RunSynchronously();
Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)
我还尝试了这里的一个建议,但是当调度程序处于挂起状态时,它不起作用。
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();
}
下面是调用runsynchronically时的异常和堆栈跟踪:
系统。InvalidOperationException
消息:在未绑定到委托的任务上不能调用runsynchronically。
InnerException:零
来源:mscorlib
加:
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.<CreateOpenPanelList>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.<CreateOpenPanelList>d__3b.MoveNext() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 625
at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.<TrySetContinuationForAwait>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()
我正在学习async/await,遇到了需要同步调用异步方法的情况。我该怎么做呢?
最好的答案是你不知道,细节取决于“情况”是什么。
它是属性getter/setter吗?在大多数情况下,使用异步方法比使用“异步属性”要好。(要了解更多信息,请参阅我关于异步属性的博客文章)。
这是一个MVVM应用程序,你想做异步数据绑定?然后使用我的NotifyTask之类的东西,正如我在MSDN关于异步数据绑定的文章中所描述的那样。
它是构造函数吗?然后,您可能会考虑使用异步工厂方法。(要了解更多信息,请参阅我关于异步构造函数的博客文章)。
几乎总有比同步胜过异步更好的答案。
如果这对您的情况来说是不可能的(您可以通过在这里询问描述该情况的问题来了解这一点),那么我建议只使用同步代码。异步方式是最好的;全程同步是第二好的。不建议使用“Sync-over-async”。
然而,在一些情况下,同步优于异步是必要的。具体来说,你受到调用代码的约束,所以你必须是同步的(并且绝对没有办法重新思考或重新构造你的代码来允许异步),你必须调用异步代码。这是一种非常罕见的情况,但它确实不时出现。
在这种情况下,你需要使用我在棕地异步开发的文章中描述的技巧之一,特别是:
Blocking (e.g., GetAwaiter().GetResult()). Note that this can cause deadlocks (as I describe on my blog).
Running the code on a thread pool thread (e.g., Task.Run(..).GetAwaiter().GetResult()). Note that this will only work if the asynchronous code can be run on a thread pool thread (i.e., is not dependent on a UI or ASP.NET context).
Nested message loops. Note that this will only work if the asynchronous code only assumes a single-threaded context, not a specific context type (a lot of UI and ASP.NET code expect a specific context).
Nested message loops are the most dangerous of all the hacks, because it causes re-entrancy. Re-entrancy is extremely tricky to reason about, and (IMO) is the cause of most application bugs on Windows. In particular, if you're on the UI thread and you block on a work queue (waiting for the async work to complete), then the CLR actually does some message pumping for you - it'll actually handle some Win32 messages from within your code. Oh, and you have no idea which messages - when Chris Brumme says "Wouldn’t it be great to know exactly what will get pumped? Unfortunately, pumping is a black art which is beyond mortal comprehension.", then we really have no hope of knowing.
当你在UI线程上这样阻塞时,你是在自找麻烦。另一个cbrumme引用了同一篇文章:“不时地,公司内部或外部的客户发现我们在STA [UI线程]的托管阻塞期间抽取消息。这是一个合理的担忧,因为他们知道在可重入性面前很难编写健壮的代码。”
是的,它是。在可重入性面前编写健壮的代码是非常困难的。嵌套的消息循环迫使您编写在可重入性面前具有健壮性的代码。这就是为什么这个问题的公认答案在实践中是极其危险的。
如果你完全没有其他的选择——你不能重新设计你的代码,你不能将它重构为异步的——你被不可更改的调用代码强制为同步的——你不能将下游代码更改为同步的——你不能阻塞——你不能在一个单独的线程上运行异步代码——这时,只有在那时,你才应该考虑采用可重入性。
如果您发现自己处于这种情况,我建议使用Dispatcher之类的工具。WPF应用程序的PushFrame,循环应用程序。WinForm应用的DoEvents,一般情况下,我自己的AsyncContext.Run。
我正在学习async/await,遇到了需要同步调用异步方法的情况。我该怎么做呢?
最好的答案是你不知道,细节取决于“情况”是什么。
它是属性getter/setter吗?在大多数情况下,使用异步方法比使用“异步属性”要好。(要了解更多信息,请参阅我关于异步属性的博客文章)。
这是一个MVVM应用程序,你想做异步数据绑定?然后使用我的NotifyTask之类的东西,正如我在MSDN关于异步数据绑定的文章中所描述的那样。
它是构造函数吗?然后,您可能会考虑使用异步工厂方法。(要了解更多信息,请参阅我关于异步构造函数的博客文章)。
几乎总有比同步胜过异步更好的答案。
如果这对您的情况来说是不可能的(您可以通过在这里询问描述该情况的问题来了解这一点),那么我建议只使用同步代码。异步方式是最好的;全程同步是第二好的。不建议使用“Sync-over-async”。
然而,在一些情况下,同步优于异步是必要的。具体来说,你受到调用代码的约束,所以你必须是同步的(并且绝对没有办法重新思考或重新构造你的代码来允许异步),你必须调用异步代码。这是一种非常罕见的情况,但它确实不时出现。
在这种情况下,你需要使用我在棕地异步开发的文章中描述的技巧之一,特别是:
Blocking (e.g., GetAwaiter().GetResult()). Note that this can cause deadlocks (as I describe on my blog).
Running the code on a thread pool thread (e.g., Task.Run(..).GetAwaiter().GetResult()). Note that this will only work if the asynchronous code can be run on a thread pool thread (i.e., is not dependent on a UI or ASP.NET context).
Nested message loops. Note that this will only work if the asynchronous code only assumes a single-threaded context, not a specific context type (a lot of UI and ASP.NET code expect a specific context).
Nested message loops are the most dangerous of all the hacks, because it causes re-entrancy. Re-entrancy is extremely tricky to reason about, and (IMO) is the cause of most application bugs on Windows. In particular, if you're on the UI thread and you block on a work queue (waiting for the async work to complete), then the CLR actually does some message pumping for you - it'll actually handle some Win32 messages from within your code. Oh, and you have no idea which messages - when Chris Brumme says "Wouldn’t it be great to know exactly what will get pumped? Unfortunately, pumping is a black art which is beyond mortal comprehension.", then we really have no hope of knowing.
当你在UI线程上这样阻塞时,你是在自找麻烦。另一个cbrumme引用了同一篇文章:“不时地,公司内部或外部的客户发现我们在STA [UI线程]的托管阻塞期间抽取消息。这是一个合理的担忧,因为他们知道在可重入性面前很难编写健壮的代码。”
是的,它是。在可重入性面前编写健壮的代码是非常困难的。嵌套的消息循环迫使您编写在可重入性面前具有健壮性的代码。这就是为什么这个问题的公认答案在实践中是极其危险的。
如果你完全没有其他的选择——你不能重新设计你的代码,你不能将它重构为异步的——你被不可更改的调用代码强制为同步的——你不能将下游代码更改为同步的——你不能阻塞——你不能在一个单独的线程上运行异步代码——这时,只有在那时,你才应该考虑采用可重入性。
如果您发现自己处于这种情况,我建议使用Dispatcher之类的工具。WPF应用程序的PushFrame,循环应用程序。WinForm应用的DoEvents,一般情况下,我自己的AsyncContext.Run。
如果我没看错你的问题——想要同步调用异步方法的代码正在挂起的调度程序线程上执行。你想要同步地阻塞那个线程直到async方法完成。
c# 5中的异步方法是通过有效地将方法切碎,并返回一个可以跟踪整个shabang的整体完成的Task来实现的。然而,被分割的方法如何执行取决于传递给await操作符的表达式类型。
大多数情况下,您将在Task类型的表达式上使用await。Task对await模式的实现是“智能的”,因为它遵循SynchronizationContext,这基本上会导致以下情况发生:
如果进入await的线程位于Dispatcher或WinForms消息循环线程上,它将确保async方法的块作为消息队列处理的一部分发生。
如果进入await的线程位于线程池线程上,则异步方法的剩余块发生在线程池的任何位置。
这就是为什么您可能会遇到问题——异步方法实现试图在Dispatcher上运行其余部分——即使它被挂起。
.... 备份!....
我要问一个问题,为什么你要在异步方法上同步阻塞?这样做会违背为什么要异步调用该方法的目的。通常,当您开始在Dispatcher或UI方法上使用await时,您将希望将整个UI流转为异步。例如,如果你的调用堆栈如下所示:
(高级)WebRequest.GetResponse ()
YourCode.HelperMethod ()
YourCode.AnotherMethod ()
YourCode.EventHandlerMethod ()
[UI Code].Plumbing() - WPF或WinForms代码
[消息循环]- WPF或WinForms消息循环
然后,一旦代码转换为使用异步,您通常会得到
(高级)WebRequest.GetResponseAsync ()
YourCode.HelperMethodAsync ()
YourCode.AnotherMethodAsync ()
YourCode.EventHandlerMethodAsync ()
[UI Code].Plumbing() - WPF或WinForms代码
[消息循环]- WPF或WinForms消息循环
实际回答
上面的AsyncHelpers类实际上可以工作,因为它的行为就像一个嵌套的消息循环,但它将自己的并行机制安装到Dispatcher上,而不是试图在Dispatcher本身上执行。这是解决你的问题的一个办法。
另一种解决方法是在线程池线程上执行async方法,然后等待它完成。这样做很容易-你可以用下面的代码片段来做:
var customerList = TaskEx.RunEx(GetCustomers).Result;
最终的API将是Task.Run(…),但对于CTP,您将需要Ex后缀(此处解释)。