如何同步运行异步 Task<T> 方法?

How would I run an async Task<T> method synchronously?

提问人:Rachel 提问时间:2/24/2011 最后编辑:John SmithRachel 更新时间:6/27/2023 访问量:539638

问:

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

异步方法:

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

以下是调用的异常和堆栈跟踪:RunSynchronously

System.InvalidOperationException

消息:不能在未绑定到委托的任务上调用 RunSynchronously。

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()
C C#-5.0 异步等待

评论

53赞 Stephen Cleary 10/16/2013
“如何同步调用异步方法”这个问题的最佳答案是“不要”。有一些技巧可以试图迫使它工作,但它们都有非常微妙的陷阱。相反,请备份并修复使您“需要”执行此操作的代码。
91赞 Contango 9/13/2014
@Stephen Cleary 绝对同意,但有时这是不可避免的,例如当您的代码依赖于某些不使用 async/await 的第三方 API 时。此外,如果在使用 MVVM 时绑定到 WPF 属性,则实际上不可能使用 async/await,因为属性不支持这样做。
5赞 Dinei 10/26/2015
@StephenCleary 并非总是如此。我正在构建一个DLL,它将导入GeneXus。它不支持 async/await 关键字,因此我必须只使用同步方法。
7赞 Dinei 10/27/2015
@StephenCleary 1) GeneXus 是第 3 个 pt 工具,我无法访问其源代码;2)GeneXus甚至没有“函数”的实现,所以我无法意识到如何用这种东西实现“回调”。当然,这将比同步使用更难解决;3)我正在将GeneXus与MongoDB C#驱动程序集成,该驱动程序仅异步公开了一些方法Task
9赞 ygoe 9/26/2016
@StephenCleary 这都是很好的理论,但“不要这样做”有一个固有的问题,即它“不起作用”。C# 主动禁止我在同步块中使用。我应该让 Microsoft 更改他们的语言吗?或者我应该放弃同步并接受混乱的数据结构? 是癌症,与其说是GPL,不如说是GPL。一旦你拥有它,你就无法摆脱它。awaitasync

答:

5赞 Daniel A. White 2/24/2011 #1

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

Service.GetCustomers();

那不是异步的。

评论

4赞 Rachel 2/24/2011
如果我无法做到这一点,那将是我所做的......除了异步版本之外,还创建同步版本
0赞 Ferenc Dajka 7/23/2020
如果由于使用库而无法编写自己的函数怎么办?
7赞 Dan Abramov 2/24/2011 #2

在您的代码中,您的第一个等待任务执行,但您尚未启动它,因此它会无限期地等待。试试这个:

Task<Customer> task = GetCustomers();
task.RunSynchronously();

编辑:

你说你得到了一个例外。请发布更多详细信息,包括堆栈跟踪。
Mono 包含以下测试用例:

[Test]
public void ExecuteSynchronouslyTest ()
{
        var val = 0;
        Task t = new Task (() => { Thread.Sleep (100); val = 1; });
        t.RunSynchronously ();

        Assert.AreEqual (1, val);
}

检查这是否适合您。如果没有,尽管可能性很小,但您可能有一些奇怪的异步 CTP 版本。如果它确实有效,您可能需要检查编译器究竟生成了什么,以及实例化与此示例有何不同。Task

编辑#2:

我用 Reflector 检查了您描述的异常发生在 .这有点奇怪,但我不是异步 CTP 方面的专家。正如我所说,你应该反编译你的代码,看看它是如何被实例化的。m_actionnullTaskm_actionnull

评论

0赞 Rachel 2/24/2011
我调整了我的问题,使我尝试的代码更清晰一些。RunSynchronously 返回错误 。谷歌无济于事,因为所有结果都是中文的......RunSynchronously may not be called on a task unbound to a delegate
0赞 Rachel 2/24/2011
我认为区别在于我不会创建任务然后尝试运行它。相反,当使用关键字时,任务是由 async 方法创建的。在我之前的评论中发布的例外是我得到的例外,尽管它是我无法谷歌搜索并找到原因或解决方案的少数例外之一。await
1赞 Dan Abramov 2/24/2011
async而关键词只不过是语法糖。编译器生成要创建的代码,因此我首先要看的地方。至于异常,你只发布了异常消息,如果没有异常类型和堆栈跟踪,这是无用的。调用异常的方法并在问题中发布输出。asyncTask<Customer>GetCustomers()ToString()
0赞 Rachel 2/24/2011
@gaearon:我在原来的问题中发布了异常详细信息和堆栈跟踪。
4赞 sgnsajgon 9/16/2014
@gaearon 我认为你投了反对票,因为你的帖子不适用于问题。讨论的是 async-await 方法,而不是简单的 Task 返回方法。此外,在我看来,async-await 机制是一种语法糖,但并不是那么微不足道——有延续、上下文捕获、本地上下文恢复、增强的本地异常处理等等。然后,不应对异步方法的结果调用 RunSynchronously 方法,因为根据定义,异步方法应返回当前至少已计划的 Task,并且多次处于运行状态。
536赞 Rachel 2/24/2011 #3

这是我发现的一种解决方法,适用于所有情况(包括暂停的调度员)。这不是我的代码,我仍在努力完全理解它,但它确实有效。

可以使用以下命令调用它:

customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());

代码来自这里

public static class AsyncHelpers
{
    /// <summary>
    /// Synchronously execute's an async Task method which has a void return value.
    /// </summary>
    /// <param name="task">The Task method to execute.</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var syncContext = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(syncContext);
        
        syncContext.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                syncContext.InnerException = e;
                throw;
            }
            finally
            {
                syncContext.EndMessageLoop();
            }
        }, null);
        
        syncContext.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Synchronously execute's an async Task<T> method which has a T return type.
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">The Task<T> method to execute.</param>
    /// <returns>The result of awaiting the given Task<T>.</returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var syncContext = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(syncContext);
        T result;
        
        syncContext.Post(async _ =>
        {
            try
            {
                result = await task();
            }
            catch (Exception e)
            {
                syncContext.InnerException = e;
                throw;
            }
            finally
            {
                syncContext.EndMessageLoop();
            }
        }, null);
        
        syncContext.BeginMessageLoop();
        
        SynchronizationContext.SetSynchronizationContext(oldContext);
        
        return result;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        private readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();
        private bool done;
        
        public Exception InnerException { get; set; }

        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<SendOrPostCallback, 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;
        }
    }
}

评论

32赞 Cameron MacFarland 2/9/2013
关于它如何工作的一些背景,Stephen Toub(Mr Parallel)写了一系列关于这一点的文章。第 1 部分 第 2 部分 第 3 部分
18赞 Tom Jacques 6/26/2013
我更新了 John 的代码,使其无需将任务包装在 lambda 中即可工作:github.com/tejacques/AsyncBridge。从本质上讲,您使用 using 语句处理异步块。using 块内的任何内容都是异步发生的,最后需要等待。缺点是你需要在回调中自己解开任务,但它仍然相当优雅,特别是当你需要同时调用多个异步函数时。
21赞 justin.lovell 1/23/2014
@StephenCleary 虽然我大体上同意你的观点,即代码应该一直保持异步,但有时你会发现自己处于一种不可行的情况,必须强制它作为同步调用。基本上,我的情况是我的所有数据访问代码都是异步方式的。我需要根据站点地图构建站点地图,而我使用的第三方库是 MvcSitemap。现在,当通过基类扩展它时,不能将其声明为方法。要么我不得不用一个新库替换,要么只是调用同步操作。DynamicNodeProviderBaseasync
7赞 Stephen Cleary 1/23/2014
@justin.lovell:是的,库的限制会迫使我们进行黑客攻击,至少在库更新之前是这样。听起来 MvcSitemap 就是这样一种需要黑客攻击的情况(MVC 过滤器和子操作也是如此);我只是劝阻人们不要这样做,因为像这样的黑客在不必要的时候经常被使用。特别是对于 MVC,一些 ASP.NET/MVC API 确实假设它们有一个 ,所以如果你调用这些 API,这个特定的 hack 将不起作用。AspNetSynchronizationContext
6赞 ZunTzu 10/28/2015
此代码将不起作用。如果从池线程调用它,则可能会触发线程匮乏死锁。调用方将阻止等待操作完成,如果调用方已耗尽线程池,则可能永远不会发生这种情况。请参阅此文章
26赞 Theo Yaung 2/25/2011 #4

如果我没看错你的问题 - 想要同步调用异步方法的代码正在挂起的调度程序线程上执行。并且您希望实际同步阻止该线程,直到异步方法完成。

C# 5 中的异步方法通过有效地将方法切成多个部分来支持,并返回一个可以跟踪整个 shabang 的整体完成情况。但是,切碎方法的执行方式可能取决于传递给运算符的表达式的类型。Taskawait

大多数情况下,您将在 type 上使用 .Task 对模式的实现是“智能的”,因为它遵循 ,这基本上会导致以下情况发生:awaitTaskawaitSynchronizationContext

  1. 如果进入 的线程位于 Dispatcher 或 WinForms 消息循环线程上,则它确保异步方法的块作为消息队列处理的一部分发生。await
  2. 如果进入 的线程位于线程池线程上,则异步方法的剩余块将出现在线程池上的任何位置。await

这就是为什么您可能会遇到问题的原因 - 异步方法实现正在尝试在 Dispatcher 上运行其余部分 - 即使它已挂起。

....备份!....

我不得不问一个问题,你为什么要尝试同步阻止异步方法?这样做会破坏为什么要异步调用该方法的目的。通常,当您开始使用 Dispatcher 或 UI 方法时,您需要将整个 UI 流设置为异步。例如,如果您的调用堆栈如下所示:await

  1. [返回顶部] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing() - WPF或代码WinForms
  6. [Message Loop] - 或消息循环WPFWinForms

然后,一旦代码转换为使用异步,通常最终会得到

  1. [返回顶部] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing() - WPF或代码WinForms
  6. [Message Loop] - 或消息循环WPFWinForms

实际回答

上面的 AsyncHelpers 类实际上有效,因为它的行为类似于嵌套的消息循环,但它将自己的并行机制安装到 Dispatcher 中,而不是尝试在 Dispatcher 本身上执行。这是解决您问题的一种解决方法。

另一种解决方法是在线程池线程上执行异步方法,然后等待它完成。这样做很容易 - 您可以使用以下代码片段来完成:

var customerList = TaskEx.RunEx(GetCustomers).Result;

最终的 API 将是 Task.Run(...),但使用 CTP 时,您将需要 Ex 后缀(此处解释)。

评论

0赞 Rachel 2/25/2011
+1 表示详细说明,但是,当应用程序在挂起的调度程序线程上运行时,应用程序会挂起。此外,GetCustomers() 方法通常是异步运行的,但是在一种情况下它需要同步运行,因此我一直在寻找一种方法来做到这一点,而无需构建该方法的同步版本。TaskEx.RunEx(GetCustomers).Result
0赞 Stephen Cleary 9/5/2013
+1 表示“为什么要尝试同步阻止异步方法?总有一种方法可以正确使用方法;当然应该避免嵌套循环。async
14赞 RredCat 6/21/2012 #5

请注意 - 这种方法:

Task<Customer> task = GetCustomers();
task.Wait()

适用于 WinRT。

让我解释一下:

private void TestMethod()
{
    Task<Customer> task = GetCustomers(); // call async method as sync and get task as result
    task.Wait(); // wait executing the method
    var customer = task.Result; // get's result.
    Debug.WriteLine(customer.Name); //print customer name
}
public class Customer
{
    public Customer()
    {
        new ManualResetEvent(false).WaitOne(TimeSpan.FromSeconds(5));//wait 5 second (long term operation)
    }
    public string Name { get; set; }
}
private Task<Customer> GetCustomers()
{
    return Task.Run(() => new Customer
    {
        Name = "MyName"
    });
}

此外,此方法仅适用于 Windows 应用商店解决方案!

注意:如果您在其他异步方法中调用您的方法(根据@Servy的评论),则这种方式不是线程安全的

评论

0赞 RredCat 12/17/2013
我解释了这个解决方案,检查编辑部分。
2赞 Servy 12/17/2013
在异步情况下调用时,这很容易导致死锁。
0赞 RredCat 12/17/2013
@Servy说得通。因此,当我正确使用 Wait(timeOut) 会有所帮助时,对吧?
1赞 Servy 12/17/2013
然后,您需要担心在操作未实际完成时达到超时,这是非常糟糕的,并且在死锁的情况下等待超时所花费的时间(在这种情况下,您仍在继续未完成)。所以不,这并不能解决问题。
0赞 RredCat 12/17/2013
@Servy 看来我必须为我的解决方案实现。CancellationToken
365赞 AK_ 8/3/2012 #6

请注意,这个答案已经三年了。我主要基于 .Net 4.0 的经验编写它,很少使用 4.5,尤其是 . 一般来说,这是一个不错的简单解决方案,但有时会破坏事情。请阅读评论中的讨论。async-await

.净 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、Task.Result、Task.RunSynchronously


.Net 4.0

使用这个:

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

x.AsyncWaitHandle.WaitOne();

...或者这个:

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

评论

76赞 Jordy Langen 8/23/2013
.Result在某些情况下可能会产生死锁
137赞 Stephen Cleary 9/5/2013
Result很容易导致异步代码死锁,正如我在博客上所描述的那样。
10赞 AK_ 10/16/2013
@StephenCleary我读了你的帖子,并亲自尝试过。老实说,我认为微软的某个人真的喝醉了......这与winforms和后台线程是相同的问题。
12赞 sgnsajgon 9/13/2014
该问题涉及由异步方法返回的 Task。此类任务可能已启动、执行或取消,因此使用 Task.RunSynchronously 方法可能会导致 InvalidOperationException。请参阅 MSDN 页面:Task.RunSynchronously 方法。此外,该 Task 可能是由 Task.Factory.StartNewTask.Run 方法(在异步方法内部)创建的,因此尝试再次启动它是危险的。某些争用条件可能会在运行时发生。另一方面,Task.Wait 和 Task.Result 可能会导致死锁
6赞 JonnyRaa 9/25/2014
同步运行对我有用...我不知道我是否遗漏了什么,但这似乎比标记答案的恐怖更可取 - 我只是在寻找一种关闭异步以测试代码的方法,只是为了阻止 ui 挂起
81赞 Michael L Perry 6/14/2013 #7

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

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; 

评论

3赞 Michael L Perry 4/9/2014
然后你调用任务。Wait() 中。数据类型只是 Task。
1赞 sgnsajgon 9/13/2014
假设 DoSomethingAsync() 是一个长期运行的异步方法(在内部它等待一个长时间运行的任务),但它会快速将流控制返回给其调用者,因此 lambda 参数工作也很快结束。Tusk.Run() 的结果可能是 Task<Task> 或 Task<Task<>>,因此您正在等待外部任务的结果,该结果快速完成,但内部任务(由于在异步方法中等待长时间运行的作业)仍在运行。结论是,我们可能需要使用 Unwrap() 方法(就像在 @J.Lennon 帖子中所做的那样)来实现异步方法的同步行为。
5赞 ZunTzu 11/2/2015
@sgnsajgon 你错了。Task.Run 与 Task.Factory.StartNew 的不同之处在于,它已经自动解开了结果。请参阅此文章
1赞 ygoe 12/14/2017
我可以写吗?这将删除一个级别的委托。Task.Run(DoSomethingAsync)
1赞 Michael L Perry 2/21/2018
是的。但是,相反的方向,如 in 更明确,并通过@sgnsajgon它可能返回 Task<Task<MyResult>> 来解决问题。无论哪种方式,都选择了 Task.Run 的正确重载,但异步委托使你的意图显而易见。Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());
-5赞 ksemenenko 9/28/2013 #8
    private int GetSync()
    {
        try
        {
            ManualResetEvent mre = new ManualResetEvent(false);
            int result = null;

            Parallel.Invoke(async () =>
            {
                result = await SomeCalcAsync(5+5);
                mre.Set();
            });

            mre.WaitOne();
            return result;
        }
        catch (Exception)
        {
            return null;
        }
    }
17赞 J. Lennon 10/5/2013 #9

我遇到过几次,主要是在单元测试或 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");

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

评论

0赞 AndreFeijo 2/17/2017
这是唯一一个没有让我陷入僵局的。
1赞 nawfal 8/4/2020
@AndreFeijo我不知道它是什么,但这本质上是(略有调整)。两者都应该有效。Task.Run(() => ..).Wait()
25赞 Clement 7/23/2014 #10

这对我来说效果很好

public static class TaskHelper
{
    public static void RunTaskSynchronously(this Task t)
    {
        var task = Task.Run(async () => await t);
        task.Wait();
    }

    public static T RunTaskSynchronously<T>(this Task<T> t)
    {
        T res = default(T);
        var task = Task.Run(async () => res = await t);
        task.Wait();
        return res;
    }
}

评论

0赞 sgnsajgon 9/16/2014
您还需要使用 Task.Unwrap 方法,因为您的 Task.Wait 语句会导致等待外部 Task(由 Task.Run 创建),而不是等待作为扩展方法的参数传递的内部 await t TaskTask.Run 方法返回的不是 Task<T>,而是 Task<Task<T>>。在某些简单方案中,由于 TaskScheduler 优化,您的解决方案可能会起作用,例如,在等待操作期间使用 TryExecuteTaskInline 方法在当前线程中执行任务。请看我对这个答案的评论。
1赞 Clement 9/16/2014
这是不正确的。Task.Run 将返回 Task<T>。请参阅此重载 msdn.microsoft.com/en-us/library/hh194918(v=vs.110.aspx
1赞 ygoe 9/26/2016
这应该如何使用?WPF 中出现此死锁:MyAsyncMethod().RunTaskSynchronously();
1赞 nawfal 8/4/2020
这仅适用于没有同步上下文的平台(控制台应用、ASP.NET 核心应用等)。对于具有同步上下文的平台,这仅适用于冷任务,也就是不适用于 99% 的正常情况。对于已经开始的任务,将其包装在 .换句话说,在正常用法中,例如 UI 应用的挂起。Task.RunGetFromNetworkAsync().RunTaskSynchronously()
3赞 Contango 9/13/2014 #11

此答案适用于使用 WPF for .NET 4.5 的任何人。

如果尝试在 GUI 线程上执行,则函数定义中没有关键字,则将无限期挂起。Task.Run()task.Wait()async

此扩展方法通过检查我们是否在 GUI 线程上,如果是,则在 WPF 调度程序线程上运行任务来解决此问题。

在不可避免的情况下,此类可以充当 async/await 环境和非异步/await 环境之间的粘合剂,例如 MVVM 属性或对不使用 async/await 的其他 API 的依赖关系。

/// <summary>
///     Intent: runs an async/await task synchronously. Designed for use with WPF.
///     Normally, under WPF, if task.Wait() is executed on the GUI thread without async
///     in the function signature, it will hang with a threading deadlock, this class 
///     solves that problem.
/// </summary>
public static class TaskHelper
{
    public static void MyRunTaskSynchronously(this Task task)
    {
        if (MyIfWpfDispatcherThread)
        {
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E99213. Task did not run to completion.");
            }
        }
        else
        {
            task.Wait();
            if (task.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E33213. Task did not run to completion.");
            }
        }
    }

    public static T MyRunTaskSynchronously<T>(this Task<T> task)
    {       
        if (MyIfWpfDispatcherThread)
        {
            T res = default(T);
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { res = await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E89213. Task did not run to completion.");
            }
            return res;
        }
        else
        {
            T res = default(T);
            var result = Task.Run(async () => res = await task);
            result.Wait();
            if (result.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E12823. Task did not run to completion.");
            }
            return res;
        }
    }

    /// <summary>
    ///     If the task is running on the WPF dispatcher thread.
    /// </summary>
    public static bool MyIfWpfDispatcherThread
    {
        get
        {
            return Application.Current.Dispatcher.CheckAccess();
        }
    }
}
17赞 wenhx 12/2/2014 #12

我在Microsoft.AspNet.Identity.Core组件中找到了此代码,并且它可以工作。

private static readonly TaskFactory _myTaskFactory = new 
     TaskFactory(CancellationToken.None, TaskCreationOptions.None, 
     TaskContinuationOptions.None, TaskScheduler.Default);

// Microsoft.AspNet.Identity.AsyncHelper
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
    CultureInfo cultureUi = CultureInfo.CurrentUICulture;
    CultureInfo culture = CultureInfo.CurrentCulture;
    return AsyncHelper._myTaskFactory.StartNew<Task<TResult>>(delegate
    {
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = cultureUi;
        return func();
    }).Unwrap<TResult>().GetAwaiter().GetResult();
}

评论

0赞 Anton 8/18/2022
我想知道为什么会调用 Unwrap<TResult> ()?
188赞 James Ko 2/8/2016 #13

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

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

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

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

  • 它不会吞下异常(如Wait)
  • 它不会包装任何抛出的异常(如AggregateExceptionResult)
  • 两者都有效(自己试试吧!TaskTask<T>)

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


编辑:请注意,这种方法(或使用)可能会死锁,除非您确保在每次等待时都添加所有可能从中访问的异步方法(而不仅仅是它直接调用的方法)。解释.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()

评论

1赞 Lee McPherson 4/7/2016
对我有用,简单的东西。此外,如果该方法返回 IAsyncOperation,我必须先将其转换为 Task:BlahAsync()。AsTask() 中。GetAwaiter() 中。GetResult();
6赞 Augusto Barreto 12/13/2017
这导致了 asmx Web 方法内部的死锁。尽管如此,将方法调用包装在 Task.Run() 中使其起作用:Task.Run(() => BlahAsync())。GetAwaiter() 中。GetResult()
1赞 dythim 1/30/2018
在语法上,我最喜欢这种方法,因为它不涉及 lambda。
34赞 Rachel 3/2/2018
请不要编辑其他人的答案以插入指向您自己的链接。如果您认为您的答案更好,请将其作为评论保留。
1赞 Mike Grove aka Theophilus 9/5/2019
learn.microsoft.com/en-us/dotnet/api/......说,“这种方法是供编译器用户使用的,而不是直接在代码中使用。GetAwaiter()
1赞 donttellya 2/16/2016 #14

我认为以下辅助方法也可以解决问题。

private TResult InvokeAsyncFuncSynchronously<TResult>(Func< Task<TResult>> func)
    {
        TResult result = default(TResult);
        var autoResetEvent = new AutoResetEvent(false);

        Task.Run(async () =>
        {
            try
            {
                result = await func();
            }
            catch (Exception exc)
            {
                mErrorLogger.LogError(exc.ToString());
            }
            finally
            {
                autoResetEvent.Set();
            }
        });
        autoResetEvent.WaitOne();

        return result;
    }

可以通过以下方式使用:

InvokeAsyncFuncSynchronously(Service.GetCustomersAsync);

评论

0赞 tmt 5/18/2016
它不是真正的“同步”。Y ou 创建两个线程并等待另一个线程的第一个结果。
0赞 Dan 9/13/2016
撇开所有事情不谈,这是一个非常糟糕的主意。
1赞 tmrog 6/20/2017
我只是编写了几乎相同的代码(逐行相同),但使用 SemaphoreSlim 而不是自动重置事件。希望我早点看到这个。我发现这种方法可以防止死锁,并使您的异步代码运行与在真正的异步场景中相同。不太确定为什么这是一个坏主意。似乎比我上面看到的其他方法干净得多。
0赞 tmrog 6/20/2017
@DanPantry 我现在实际上看到一些我不明白的方法的僵局。你能详细解释一下为什么这是一个坏主意吗?
0赞 tmrog 6/20/2017
我的错。我明白了。这现在可以工作了。我的问题是我在主线程上创建任务,然后将该任务传递给调用异步方法。感谢您@donttellya代码帮助了我。
73赞 Stephen Cleary 9/7/2016 #15

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

最好的答案是你没有,细节取决于“情况”是什么。

它是属性的 getter/setter 吗?在大多数情况下,异步方法比“异步属性”更好。(有关详细信息,请参阅我关于异步属性的博客文章)。

这是一个 MVVM 应用,并且您想要执行异步数据绑定吗?然后使用类似我的 NotifyTask 的东西,如我关于异步数据绑定的 MSDN 文章中所述。

是构造函数吗?然后,您可能需要考虑异步工厂方法。(有关详细信息,请参阅我关于异步构造函数的博客文章)。

几乎总是有比异步同步更好的答案。

如果您的情况无法做到这一点(并且您通过在此处提出描述情况的问题来了解这一点),那么我建议只使用同步代码。一路异步是最好的;一路同步是第二好的。不建议异步同步。

但是,在少数情况下,异步同步是必要的。具体来说,你受到调用代码的约束,因此你必须同步(并且绝对没有办法重新思考或重新构建你的代码以允许异步),并且你必须调用异步代码。 这是一种非常罕见的情况,但确实时有发生。

在这种情况下,您需要使用我关于棕地异步开发的文章中描述的技巧之一,特别是:

  • 阻塞(例如,)。请注意,这可能会导致死锁(正如我在博客中描述的那样)。GetAwaiter().GetResult()
  • 在线程池线程上运行代码(例如,)。请注意,仅当异步代码可以在线程池线程上运行(即不依赖于 UI 或 ASP.NET 上下文)时,这才有效。Task.Run(..).GetAwaiter().GetResult()
  • 嵌套消息循环。请注意,仅当异步代码仅假定单线程上下文而不是特定上下文类型(许多 UI 和 ASP.NET 代码需要特定上下文)时,这才有效。

嵌套消息循环是所有黑客中最危险的,因为它会导致重入。重入是非常棘手的推理,(IMO)是Windows上大多数应用程序错误的原因。具体而言,如果你在 UI 线程上,并且阻塞了工作队列(等待异步工作完成),则 CLR 实际上会为你执行一些消息提取 - 它实际上会从你的代码中处理一些 Win32 消息。哦,你不知道哪些消息 - 当克里斯·布鲁姆说“确切地知道什么会被抽走不是很好吗?不幸的是,抽水是一种超出凡人理解的黑色艺术“,那么我们真的没有希望知道。

所以,当你在UI线程上这样阻止时,你就是在自找麻烦。同一篇文章中的另一段话说:“公司内部或外部的客户不时会发现,我们在 STA [UI 线程] 上的托管阻止期间正在抽取消息。这是一个合理的担忧,因为他们知道,在面对重入时,很难写出健壮的代码。

是的,它是。很难写出在面对重入时健壮的代码。嵌套消息循环迫使您编写在面对重入时健壮的代码。这就是为什么这个问题被接受(和投票最多)的答案在实践中是极其危险的

如果你完全没有其他选择——你不能重新设计你的代码,你不能把它重构为异步——你被不可更改的调用代码强制同步——你不能将下游代码更改为同步——你不能阻止——你不能在单独的线程上运行异步代码——那么,也只有这样,你才应该考虑接受重入。

如果您确实发现自己处于这个角落,我建议对 WPF 应用程序使用 Dispatcher.PushFrame,对 WinForm 应用程序使用循环,对于一般情况,使用我自己的 AsyncContext.RunApplication.DoEvents

评论

2赞 Stephen Cleary 2/5/2017
@AlexeiLevenkov:我觉得这样做不对,原因如下:1)相关问题的答案已经过时了。2)我写了一篇关于这个主题的整篇文章,我觉得它比任何现有的SO Q/A都更完整。 3)关于这个问题的公认答案非常受欢迎。4)我强烈反对这个公认的答案。因此,将其作为欺骗关闭将是滥用权力;将其作为对此(或合并)的复制将赋予更危险的答案。我顺其自然,把它留给社区。
14赞 Mark Amery 6/13/2017
这个答案在我的脑海中走了很长一段路。“一直使用异步”是令人困惑的建议,因为显然不可能遵循。具有异步方法的程序无法编译;在某些时候,你必须弥合同步和异步世界之间的差距。这不是非常罕见的情况”,实际上每个调用异步方法的程序都是必要的。没有不“执行异步同步”的选项,只是一个选项,可以将该负担分流到调用方法,而不是在当前编写的方法中承担它。Main()
3赞 Stephen Cleary 6/13/2017
@MarkAmery:在控制台应用的方法中,异步同步是必需的。 ASP.NET、单元测试框架和每个 UI 系统都原生支持异步。即使你的所有应用都是控制台应用,你也只需要对每个应用执行一次异步同步(当然,尚不支持异步的库回调可能需要额外的技巧)。Main
1赞 ygoe 12/14/2017
伟大。我现在要在我的应用程序中使用所有方法。这是很多。这不能只是默认吗?async
1赞 S.Serpooshan 3/12/2019
值得一提的是,正如您回复我并基于您在这里的博客,僵局对 ASP.NET Core 来说不是问题!
2赞 Mahesh 10/19/2016 #16

使用以下代码片段

Task.WaitAll(Task.Run(async () => await service.myAsyncMethod()));

评论

2赞 nawfal 8/4/2020
Task.WaitAll 在此处不添加任何内容。为什么不直接等待呢?
15赞 pixel 10/26/2016 #17

我发现在不阻塞 UI 线程的情况下同步运行任务的最简单方法是使用 RunSynchronously(),例如:

Task t = new Task(() => 
{ 
   //.... YOUR CODE ....
});
t.RunSynchronously();

就我而言,我有一个在发生某些事情时触发的事件。我不知道它会发生多少次。因此,我在事件中使用了上面的代码,因此每当它触发时,它都会创建一个任务。任务是同步执行的,这对我来说效果很好。我只是感到惊讶的是,考虑到它是多么简单,我花了这么长时间才发现这一点。通常,建议要复杂得多,也容易出错。这是简单而干净的。

评论

1赞 S.Serpooshan 9/10/2018
但是,当异步代码返回我们需要的东西时,我们如何使用这种方法呢?
1赞 nawfal 8/4/2020
这适用于冷任务,而不是已经开始的任务。
0赞 JSpot 10/6/2022
这将不起作用,因为示例中的“YOUR CODE”必须是此 Task 构造函数的同步代码。但实际上,我们有异步代码,我们想把代码放在那里。
3赞 Ogglas 9/25/2017 #18

正如许多人在评论中所说的那样,简单地打电话或存在僵局的风险。由于我们大多数人都喜欢单行,因此您可以将这些用于.Result;.Wait().Net 4.5<

通过异步方法获取值:

var result = Task.Run(() => asyncGetValue()).Result;

异步调用异步方法

Task.Run(() => asyncMethod()).Wait();

不会因使用 而发生死锁问题。Task.Run

源:

https://stackoverflow.com/a/32429753/3850405

评论

0赞 Ogglas 8/9/2023
如果您投反对票,请添加评论原因。否则很难改进答案
2赞 Dan Nguyen 7/27/2018 #19

这对我有用

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    public static class AsyncHelper
    {
        private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

        public static void RunSync(Func<Task> func)
        {
            _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
        }

        public static TResult RunSync<TResult>(Func<Task<TResult>> func)
        {
            return _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
        }
    }

    class SomeClass
    {
        public async Task<object> LoginAsync(object loginInfo)
        {
            return await Task.FromResult(0);
        }
        public object Login(object loginInfo)
        {
            return AsyncHelper.RunSync(() => LoginAsync(loginInfo));
            //return this.LoginAsync(loginInfo).Result.Content;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var someClass = new SomeClass();

            Console.WriteLine(someClass.Login(1));
            Console.ReadLine();
        }
    }
}
23赞 Liang 10/24/2018 #20

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

对于返回 .Task

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

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

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

编辑

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

评论

2赞 Marek Woźniak 2/14/2019
近 8 年后我的 answar :)第二个示例 - 将在主要使用的所有计划上下文(控制台应用/.NET Core/桌面应用/...)中产生死锁。在这里,您可以更了解我现在在谈论的内容:medium.com/rubrikkgroup/......
0赞 Zodman 4/24/2020
Result如果您想要同步调用,则非常适合这项工作,否则非常危险。名称或智能意义上没有任何内容表明它是阻塞调用。它确实应该重命名。ResultResult
-4赞 Curtis 2/7/2019 #21

我发现 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() 中。它还允许您指定超时,以便在任务永远无法完成的情况下不会永远卡住。

评论

1赞 Sinatr 5/20/2019
这是轮询(旋转),委托将从池中获取线程,每秒最多 1000 次。任务完成后,它可能不会立即返回控制权(最多 10+ms 错误)。如果在超时之前完成,任务将继续运行,这使得超时实际上毫无用处。
0赞 Curtis 5/23/2019
实际上,我在我的代码中到处使用它,当满足条件时,SpinWaitSpinUntil() 会立即退出。因此,无论先到者,“满足条件”或超时,任务都会退出。它不会继续运行。
10赞 Jaider 11/17/2020 #22

注意:我认为,如果操作是异步的,则不建议更改操作的性质,因此最好的做法是按原样处理(始终异步)。通过这种方式,您可以获得其他好处,例如并行处理/多线程等。

看到其他答案没有使用这种方法,我也想在这里发布:

var customers = GetCustomersAsync().GetAwaiter().GetResult();

评论

1赞 Stack Undefined 9/6/2022
这仍然等同于调用,但现在异常被解包,而不是包装在 AggregatedException 中。var customer = GetCustomersAsync().Result
0赞 Noob 4/19/2023 #23

异步方法:

public async Task<Customers> GetCustomers()
{
    return await Service.GetCustomersAsync();
}

这似乎对我有用:

Task.Run(GetCustomers).Wait();