我正在学习异步/等待,遇到了需要同步调用异步方法的情况。我该怎么做呢?

异步方法:

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

当前回答

我遇到过几次,主要是在单元测试或windows服务开发中。目前我一直在使用这个功能:

        var runSync = Task.Factory.StartNew(new Func<Task>(async () =>
        {
            Trace.WriteLine("Task runSync Start");
            await TaskEx.Delay(2000); // Simulates a method that returns a task and
                                      // inside it is possible that there
                                      // async keywords or anothers tasks
            Trace.WriteLine("Task runSync Completed");
        })).Unwrap();
        Trace.WriteLine("Before runSync Wait");
        runSync.Wait();
        Trace.WriteLine("After runSync Waited");

这很简单,很容易,我没有任何问题。

其他回答

我遇到过几次,主要是在单元测试或windows服务开发中。目前我一直在使用这个功能:

        var runSync = Task.Factory.StartNew(new Func<Task>(async () =>
        {
            Trace.WriteLine("Task runSync Start");
            await TaskEx.Delay(2000); // Simulates a method that returns a task and
                                      // inside it is possible that there
                                      // async keywords or anothers tasks
            Trace.WriteLine("Task runSync Completed");
        })).Unwrap();
        Trace.WriteLine("Before runSync Wait");
        runSync.Wait();
        Trace.WriteLine("After runSync Waited");

这很简单,很容易,我没有任何问题。

请注意,这个答案是三年前的。我主要是根据。net 4.0的使用经验编写的,很少使用4.5,尤其是async-await。 一般来说,这是一个很好的简单的解决方案,但有时它会破坏一些东西。请阅读评论中的讨论。

net 4.5

就用这个吧:

// For Task<T>: will block until the task is completed...
var result = task.Result; 

// For Task (not Task<T>): will block until the task is completed...
task2.RunSynchronously();

看到的: TaskAwaiter, 的任务。结果, 的任务。RunSynchronously


net 4.0

用这个:

var x = (IAsyncResult)task;
task.Start();

x.AsyncWaitHandle.WaitOne();

...或:

task.Start();
task.Wait();

我知道这是一个老问题,但我想分享我的解决方案,可能不是最好的,但有效:

//Declare an Event Handler:
private event EventHandler OnThemeApply;

//..somewhere in your code..

//we 'hear' the event handler
this.OnThemeApply += (object _Sender, EventArgs _E) =>
{
  //Reaches here After the Async method had finished
  
  this.OnThemeApply = null;
};
MyAsycMethod();

private void MyAsycMethod()
{
   var t = System.Threading.Tasks.Task.Factory.StartNew(delegate
   {
      //Do something here

      Invoke((MethodInvoker)(() =>
      {
         if(this.OnThemeApply != null) this.OnThemeApply(null, null); //<- Calls the Event
      }));
   });
}

为什么不创建一个这样的呼叫:

Service.GetCustomers();

这不是异步的。

我正在学习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。