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

异步方法:

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

当前回答

在线程池上运行任务要简单得多,而不是试图欺骗调度器以同步方式运行它。这样可以确保它不会死锁。由于上下文切换会影响性能。

Task<MyResult> DoSomethingAsync() { ... }

// Starts the asynchronous task on a thread-pool thread.
// Returns a proxy to the original task.
Task<MyResult> task = Task.Run(() => DoSomethingAsync());

// Will block until the task is completed...
MyResult result = task.Result; 

其他回答

请注意,这个答案是三年前的。我主要是根据。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();

我发现SpinWait在这方面工作得很好。

var task = Task.Run(()=>DoSomethingAsyncronous());

if(!SpinWait.SpinUntil(()=>task.IsComplete, TimeSpan.FromSeconds(30)))
{//Task didn't complete within 30 seconds, fail...
   return false;
}

return true;

上述方法不需要使用. result或. wait()。它还允许您指定一个超时,以便在任务从未完成的情况下,您不会永远卡住。

令人惊讶的是没有人提到这一点:

public Task<int> BlahAsync()
{
    // ...
}

int result = BlahAsync().GetAwaiter().GetResult();

虽然不像这里的其他一些方法那么漂亮,但它有以下好处:

它不会吞下异常(像Wait) 它不会包装在AggregateException中抛出的任何异常(如Result) 适用于Task和Task<T>(自己试试!)

此外,由于GetAwaiter是鸭子类型的,这应该适用于从异步方法返回的任何对象(如ConfiguredAwaitable或YieldAwaitable),而不仅仅是任务。


edit:请注意,这种方法(或使用. result)可能会死锁,除非你确保每次等待时都添加. configureawait (false),对于所有可能从BlahAsync()到达的异步方法(不仅仅是它直接调用的方法)。解释。

// In BlahAsync() body
await FooAsync(); // BAD!
await FooAsync().ConfigureAwait(false); // Good... but make sure FooAsync() and
                                        // all its descendants use ConfigureAwait(false)
                                        // too. Then you can be sure that
                                        // BlahAsync().GetAwaiter().GetResult()
                                        // won't deadlock.

如果你懒得在所有地方添加.ConfigureAwait(false),并且你不关心性能,你也可以这样做

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()

在。net 4.6中测试。它还可以避免死锁。

对于返回Task的异步方法。

Task DoSomeWork();
Task.Run(async () => await DoSomeWork()).Wait();

对于返回Task<T>的异步方法

Task<T> GetSomeValue();
var result = Task.Run(() => GetSomeValue()).Result;

编辑:

如果调用者在线程池线程中运行(或者调用者也在任务中),在某些情况下仍然可能导致死锁。

在传统的。net中,从同步代码执行异步任务会带来一些相当大的挑战:

除非有适当的同步上下文,否则异步任务的等待操作很容易发生死锁 同步和异步代码的取消模型可能不同且不兼容

我真的认为我们应该在。net BCL中拥有这种互操作性功能。同时,你可以使用Gapotchenko.FX.Threading NuGet包中的TaskBridge类。它提供了同步和异步代码执行模型之间的无缝互操作性:

using Gapotchenko.FX.Threading.Tasks;
using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        TaskBridge.Execute(RunAsync);
    }

    static async Task RunAsync()
    {
        await Console.Out.WriteLineAsync("Hello, Async World!");
    }
}

带有TaskBridge类的NuGet包可以在这里找到。


取消模型

TaskBridge提供了不同取消模型之间的自动互操作性。

让我们从一个可以被thread . abort()方法中止的同步线程调用一个可取消的异步方法:

void SyncMethod() // can be canceled by Thread.Abort()
{
    // Executes an async task that is gracefully canceled via cancellation
    // token when current thread is being aborted or interrupted.
    TaskBridge.Execute(DoJobAsync); // <-- TaskBridge DOES THE MAGIC
}

async Task DoJobAsync(CancellationToken ct)
{
    …
    // Gracefully handles cancellation opportunities.
    ct.ThrowIfCancellationRequested();
    …
}

现在,让我们看看相反的场景,一个可取消的异步任务调用一个可取消的同步代码:

async Task DoJobAsync(CancellationToken ct) // can be canceled by a specified cancellation token
{
    // Executes a synchronous method that is thread-aborted when
    // a specified cancellation token is being canceled.
    await TaskBridge.ExecuteAsync(SyncMethod, ct); // <-- TaskBridge DOES THE MAGIC
}

void SyncMethod()
{
    …
}

上面的代码演示了在两个执行模型之间实现完全互操作性的简单一行程序。

注意:以。net Core开头的。net新版本不支持Thread.Abort()。这不是什么大问题,因为您只需将取消令牌传递给可取消方法即可。