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


628

我正在学习异步/等待,并遇到需要同步调用异步方法的情况。我怎样才能做到这一点?

异步方法:

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

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

46
问题“如何同步调用异步方法”的最佳答案是“不”。有一些骇客试图迫使它起作用,但是它们都有非常微妙的陷阱。相反,请备份并修复使您“需要”执行此操作的代码。
Stephen Cleary

57
@Stephen Cleary绝对同意,但有时它是不可避免的,例如当您的代码依赖于某些不使用异步/等待的第三方API时。此外,如果在使用MVVM时绑定到WPF属性,则实际上无法使用async / await,因为属性不支持此操作。
Contango 2014年

3
@StephenCleary并非总是如此。我正在构建一个将在GeneXus中导入的DLL。它不支持async / await关键字,因此我只能使用同步方法。
Dinei 2015年

5
@StephenCleary 1)GeneXus是第三个pt工具,我无权访问其源代码;2)GeneXus甚至没有“功能”的实现,所以我不知道如何用这种类型的东西实现“回调”。当然,这比Task同步使用更困难。3)我将GeneXus与MongoDB C#驱动程序集成在一起,后者仅异步公开某些方法
Dinei 2015年

1
@ygoe:使用异步兼容的锁,例如SemaphoreSlim
史蒂芬·克利西

Answers:


455

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

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

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

代码来自这里

public static class AsyncHelpers
{
    /// <summary>
    /// Execute's an async Task<T> method which has a void return value synchronously
    /// </summary>
    /// <param name="task">Task<T> method to execute</param>
    public static void RunSync(Func<Task> 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);
    }

    /// <summary>
    /// Execute's an async Task<T> method which has a T return type synchronously
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static T RunSync<T>(Func<Task<T>> 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<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();

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

28
为了获得有关此工作原理的一些背景知识,Stephen Toub先生(Parallel先生)撰写了一系列有关此问题的文章。第1 部分第2 部分第3部分
卡梅隆·麦克法兰

18
我更新了John的代码,使其无需在lambdas中包装任务即可工作:github.com/tejacques/AsyncBridge。本质上,您可以使用using语句来处理异步块。using块中的所有内容都是异步发生的,最后需要等待。缺点是您需要自己在回调中解包任务,但是它仍然相当优雅,尤其是当您需要一次调用多个异步函数时。
汤姆·雅克

17
@StephenCleary尽管我通常都同意代码始终是异步的,但有时您发现自己处于不可行的情况下,必须将其强制为同步调用。基本上,我的情况是我所有的数据访问代码都是异步的。我需要基于该站点地图构建一个站点地图,而我正在使用的第三方库是MvcSitemap。现在,当通过DynamicNodeProviderBase基类对其进行扩展时,无法将其声明为async方法。我要么必须替换为新的库,要么仅调用同步操作。
justin.lovell 2014年

6
@ justin.lovell:是的,库的限制可能迫使我们进行黑客攻击,至少在更新库之前。听起来MvcSitemap就是这样一种情况,需要黑客入侵(MVC过滤器和子操作也是如此);我只是劝阻人们不要这一般是因为像这样的黑客使用方式过于频繁的时候都没有必要。特别是对于MVC,某些ASP.NET/MVC API确实假定它们具有AspNetSynchronizationContext,因此,如果您调用这些API,则这种特殊的技巧将不起作用。
Stephen Cleary 2014年

5
此代码将不起作用。如果从池线程中调用它,则可能触发线程饥饿死锁。您的调用方将阻止等待操作完成,如果他用尽了线程池,则可能永远不会发生。看到这篇文章
ZunTzu

318

被告知这个答案是三岁。我主要是基于.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();

请参阅: TaskAwaiterTask.ResultTask.Run同步


.Net 4.0

用这个:

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

x.AsyncWaitHandle.WaitOne();

...或这个:

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

67
.Result可能在某些情况下产生僵局
乔迪·兰根

122
Result可以很容易导致死锁async的代码,因为我描述我的博客上。
Stephen Cleary

8
@StephenCleary我阅读了您的帖子,并亲自尝试了。老实说,我认为有人在微软是真的醉了......这是同样的问题,因为像WinForms和后台线程....
AK_

9
问题与异步方法返回的任务有关。此类Task可能已经启动,执行或取消,因此使用Task.RunSynchronously方法可能会导致InvalidOperationException。请参见MSDN页面:Task.RunSynchronously方法。此外,该Task可能是由Task.Factory.StartNewTask.Run方法(在异步方法内部)创建的,因此尝试再次启动它是很危险的。一些竞争条件可能在运行时发生。另一方面,Task.WaitTask.Result可能会导致死锁。
sgnsajgon

4
同步运行对我有用...我不知道我是否缺少什么,但这似乎比标记答案的恐怖更可取-我只是在寻找一种关闭异步的方法来测试将要停止的代码从挂起的用户界面
JonnyRaa

121

没有人惊讶地提到这一点:

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

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

虽然不如这里的其他一些方法漂亮,但是它具有以下优点:

  • 它不会吞噬异常(例如Wait
  • 它不会包装AggregateExceptionResult)中引发的任何异常
  • 同时适用于TaskTask<T>自己试试它了!

而且,由于GetAwaiter是鸭子类型的,所以它应该适用于从异步方法(如ConfiguredAwaitableYieldAwaitable)返回的任何对象,而不仅仅是Tasks。


编辑:请注意,这种方法(或使用.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
为我工作的简单的东西。另外,如果该方法返回IAsyncOperation,则必须先将其转换为Task:BlahAsync()。AsTask()。GetAwaiter()。GetResult();
Lee McPherson

3
这在asmx Web方法内部导致了死锁。不过,将方法调用包装在Task.Run()中可以使其起作用:Task.Run(()=> BlahAsync())。GetAwaiter()。GetResult()
Augusto Barreto

我在语法上最喜欢此方法,因为它不涉及lambda。
dythim

24
请不要编辑其他人的答案以插入指向您自己的链接。如果您认为答案更好,请留下评论。
雷切尔

1
docs.microsoft.com/en-us/dotnet/api/…关于GetAwaiter(),说:“此方法供编译器用户使用,而不是直接在代码中使用。”
Theophilus

75

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

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
然后调用task.Wait()。数据类型只是Task。
Michael L Perry 2014年

1
让我们假设DoSomethingAsync()总体上是长时间运行的异步方法(内部等待一个长时间运行的任务),但是它很快将流控制返回给其调用者,因此lambda参数工作也很快结束。Tusk.Run()的结果可能是Task <Task>Task <Task <>>,因此您正在等待快速完成但外部任务完成的外部任务的结果(由于等待异步方法中长时间运行的工作)仍在运行。结论是我们可能需要使用Unwrap()方法(如@ J.Lennon post中所做的那样)来实现异步方法的同步行为。
sgnsajgon

5
@sgnsajgon你错了。Task.Run与Task.Factory.StartNew的不同之处在于,它已经自动解包结果。看到这篇文章
ZunTzu

1
我可以改写Task.Run(DoSomethingAsync)吗?这将删除一级代表。
ygoe

1
是的 但是,沿相反的方向(如中所示Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());)更为明确,并通过@sgnsajgon解决了有关它可能返回Task <Task <MyResult >>的问题。无论哪种方式都可以选择Task.Run的正确重载,但是异步委托会使您的意图显而易见。
Michael L Perry

57

我正在学习异步/等待,并遇到需要同步调用异步方法的情况。我怎样才能做到这一点?

最好的答案是您不要,其细节取决于“情况”。

是财产获取者/设定者吗?在大多数情况下,拥有异步方法比“异步属性”更好。(有关更多信息,请参阅我关于异步属性的博客文章)。

这是MVVM应用程序,您要执行异步数据绑定吗?然后使用诸如my之类的东西,如我NotifyTaskMSDN上有关异步数据绑定的文章中所述。

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

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

如果您的情况不可行(并且您可以通过在此处描述情况的问题来知道这一点),那么我建议您仅使用同步代码。一路异步是最好的。一路同步是第二好的。不建议异步同步。

但是,在少数情况下,需要同步同步。具体而言,您是通过调用代码限制,让你必须要同步(与绝对没有办法重新考虑或重新架构你的代码,允许异步),异步代码来调用。这是一种非常罕见的情况,但确实时有发生。

在这种情况下,您将需要使用我在Brownfield asyncDevelopment上的文章中描述的一种技巧,特别是:

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

嵌套消息循环是所有黑客中最危险的,因为它会导致重新进入。重新进入是非常棘手的推理,并且(IMO)是Windows上大多数应用程序错误的原因。特别是,如果您在UI线程上并且阻塞了工作队列(等待异步工作完成),则CLR实际上会为您执行一些消息泵送-它实际上会处理您内部的一些Win32消息代码。噢,您不知道是哪条消息- 克里斯·布鲁姆 Chris Brumme)说:“确切知道将要被泵出的东西真是太好了吗?不幸的是,泵送是一种超越凡人理解的妖术。” ,那么我们真的没有希望知道。

因此,当您在UI线程上进行此类阻止时,您就在自找麻烦。同一篇文章的另一篇cbrumme语录:“公司内部或外部的客户有时会发现我们在STA [UI线程]上的受管阻塞期间正在发送消息。这是一个合理的问题,因为他们知道这非常困难写面对重入的鲁棒代码。”

是的。用心写的代码,在重入面对强劲。嵌套的消息循环迫使您编写面对重新进入时很健壮的代码。这就是为什么接受(和最upvoted)答案这个问题非常危险的做法。

如果您完全无法使用其他所有选项-您无法重新设计代码,也无法将其重组为异步-无法更改的调用代码迫使您同步-您无法将下游代码更改为同步-您不能阻止-您不能在单独的线程上运行异步代码-然后,只有这样,您应该考虑采用可重入性。

如果您确实在这个角落,我建议您Dispatcher.PushFrame为WPF应用程序使用类似的工具Application.DoEvents为WinForm应用程序使用类似的工具,对于一般情况,建议使用我自己的工具AsyncContext.Run


斯蒂芬,还有另一个非常相似的问题,您也提供了很棒的答案。您是否认为其中一个可以作为重复项关闭,也可以合并请求,或者首先提出meta选项(因为每个q都有200K观看次数,票数超过200票)?有什么建议吗?
阿列克谢·莱文科夫'17

1
@AlexeiLevenkov:这样做有几个原因,我感觉不对:1)链接问题的答案已经过时。2)我写了一篇关于该主题整篇文章,我觉得它比任何现有的SO Q / A都要完整。3)关于这个问题的公认答案非常受欢迎。4)我强烈反对这一公认的答案。因此,将其作为虚假的重复进行将是滥用权力。将其作为此(或合并)的重复进行关闭会进一步赋予危险的答案。我随它去,然后交给社区。
史蒂芬·克雷里

好。我将考虑以某种方式在meta上进行介绍。
阿列克谢·莱文科夫

9
这个答案对我很重要。由于显然无法遵循,“一路向下使用异步”令人困惑。带有异步Main()方法的程序无法编译;在某些时候,你已经得到了弥合同步和异步世界之间的差距。这不是非常罕见的情况”,实际上在每个调用异步方法的程序中都是必需的。没有“不同步”的选择,只有将负担分担给调用方法的选项,而不是由您当前正在编写的方法承担。
Mark Amery

1
大。我现在要async在应用程序中使用所有方法。而且很多。难道这不是默认值吗?
ygoe '17

25

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

C#5中的异步方法通过有效地将该方法切入Task幕后的各个部分,然后返回一个可跟踪整个shabang的整体完成情况的方法来提供动力。但是,切碎的方法如何执行可能取决于传递给await运算符的表达式的类型。

大多数时候,您将使用awaittype的表达式Task。Task的await模式实现是“智能的”,因为它SynchronizationContext遵循,这基本上导致发生以下情况:

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

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

.... 备份!....

我必须问一个问题,为什么要尝试在异步方法上进行同步阻止?这样做会破坏为什么要异步调用该方法的目的。通常,当您开始使用awaitDispatcher或UI方法时,您将需要使整个UI流异步。例如,如果您的调用堆栈如下所示:

  1. [最佳] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing()- WPFWinForms代码
  6. [消息循环] - WPFWinForms消息循环

然后,将代码转换为使用异步后,您通常会得到以下结果:

  1. [最佳] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing()- WPFWinForms代码
  6. [消息循环] - WPFWinForms消息循环

实际回答

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

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

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

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


+1为详细说明,但是TaskEx.RunEx(GetCustomers).Result当应用程序在挂起的调度程序线程上运行时会挂起它。另外,GetCustomers()方法通常是异步运行的,但是在一种情况下,它需要同步运行,因此我一直在寻找一种无需构建该方法的同步版本的方法。
雷切尔

+1表示“您为什么要尝试异步阻止异步方法?” 总有一种正确使用async方法的方法。当然应该避免嵌套循环。
Stephen Cleary

24

这对我来说很好

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

您还需要使用Task.Unwrap方法,因为Task.Wait语句会导致等待外部Task(由Task.Run创建),而不是等待内部Task。作为扩展方法的参数传递给Task。您的Task.Run方法返回的不是Task <T>,而是Task <Task <T >>。在一些简单的场景您的解决方案可以使用,因为的TaskScheduler优化的作品,例如TryExecuteTaskInline方法在执行当前线程内的任务等待操作。请看看我的上一个注释这个答案。
sgnsajgon

1
那是不对的。Task.Run将返回Task <T>。请参阅此重载msdn.microsoft.com/en-us/library/hh194918(v=vs.110).aspx
Clement

应该如何使用?WPF陷入僵局:MyAsyncMethod().RunTaskSynchronously();
ygoe

18

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

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

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


1
但是,当异步代码返回所需的内容时,我们如何使用此方法?
S.Serpooshan

16

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

简单,容易,而且我没有任何问题。


这是唯一没有对我造成僵局的人。
AndreFeijo

15

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


13

请注意-这种方法:

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 Store解决方案!

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


我解释了此解决方案,请检查“编辑”部分。
RredCat 2013年

2
在异步情况下调用时,这很容易导致死锁。
Servy 2013年

@Servy有意义。所以当我正确使用Wait(timeOut)可以提供帮助时,对吗?
RredCat 2013年

1
然后,您需要担心在操作未真正完成时会达到超时,这非常糟糕,并且还需要等待直到超时(如果发生死锁)所花费的时间(在这种情况下,您仍在继续关于何时未完成)。所以不,那不能解决问题。
Servy 2013年

@Servy看起来我必须CancellationToken为我的解决方案实施。
RredCat

10

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

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:

我与反射检查,当你描述的异常发生m_actionnull。这有点奇怪,但是我不是异步CTP的专家。就像我说的,你应该反编译你的代码,看看究竟是如何Task被实例化任何怎么来了m_action就是null


PS偶尔处理否决票是怎么回事?关心详细吗?


我调整了问题,使尝试的代码更加清晰。RunSynchronously返回错误RunSynchronously may not be called on a task unbound to a delegate。谷歌没有帮助,因为所有结果都是中文...
Rachel

我认为不同之处在于,我不创建任务然后尝试运行它。而是await使用关键字时,通过async方法创建任务。我在较早的评论中发布的异常是我得到的异常,尽管它是我无法找到原因或解决方案的少数几个。
雷切尔

1
asyncasync关键字不过是语法糖。编译器会生成要创建的代码Task<Customer>GetCustomers()因此这是我首先要看的地方。至于异常,您仅发布了异常消息,如果没有异常类型和堆栈跟踪,该消息将无用。调用异常的ToString()方法并在问题中发布输出。
丹·阿布拉莫夫,

@gaearon:我在原始问题中发布了异常详细信息和堆栈跟踪。
雷切尔

2
@gaearon我认为您投票赞成,因为您的帖子不适用于提问。讨论是关于异步等待方法,而不是简单的任务返回方法。而且,在我看来,异步等待机制是一种语法糖,但并不是那么简单-存在延续,上下文捕获,本地上下文恢复,增强的本地异常处理等。然后,您不应在async方法的结果上调用RunSynchronously方法,因为根据定义,异步方法应返回当前至少已调度的Task,并且多次处于运行状态。
sgnsajgon

9

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

对于异步方法returning Task

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

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

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

编辑

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


1
我将近8年的烦恼:)第二个示例-将在所有主要使用的调度上下文(控制台应用程序/ .NET Core /桌面应用程序...)中产生死锁。在这里,你有更多的概述什么我谈论现在:medium.com/rubrikkgroup/...
W92

Result如果您想要同步呼叫,则非常适合该工作,否则非常危险。名称Result或内容上没有任何内容Result表明这是阻塞呼叫。确实应该重命名。
Zodman

5

使用以下代码片段

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

4

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

Service.GetCustomers();

那不是异步的。


4
如果我无法正常工作,那将是我的工作...除了异步版本外,还创建一个同步版本
Rachel

3

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

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

此扩展方法通过检查我们是否在GUI线程上以及是否在GUI线程上运行该任务来解决该问题。

在不可避免的情况下,例如MVVM属性或对不使用异步/等待的其他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();
        }
    }
}

3

正如许多人在评论中所说,简单地调用.Result;.Wait()存在死锁的风险。由于我们大多数人都喜欢oneliners,因此您可以将其用于.Net 4.5<

通过异步方法获取值:

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

同步调用异步方法

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

使用不会出现死锁问题Task.Run

资源:

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


1

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

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

1
请解释投票情况
donttellya

2
...我仍然非常感兴趣,为什么这个答案被否决了?
donttellya '16

这不是真正的“同步”。您创建了两个线程,然后等待其他线程的第一个结果。
tmt

除了所有事情,这是一个非常糟糕的主意。
Dan Pantry

1
我只是编写了几乎相同的代码(逐行相同),但是使用了SemaphoreSlim而不是自动重置事件。希望我早点看到这个。我发现这种方法可以防止死锁,并使异步代码的运行与真正的异步情况相同。不太确定为什么这不是一个好主意。似乎比我上面看到的其他方法干净得多。
tmrog

0

这对我有用

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

-1

我发现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
反对意见表明有人不喜欢这种方法。有没有人可以对此发表评论?
Grax32 '19

在没有拒绝投票人说为什么要给予拒绝票的情况下,有人可以投票吗?:-)
柯蒂斯

1
这是轮询(旋转),委托将以每秒最多1000次的速度从池中获取线程。任务完成后,它可能不会立即返回控制(错误最多10毫秒以上)。如果超时完成任务,则该任务将继续运行,这实际上使超时变得毫无用处。
锡纳特尔

实际上,我在代码中到处使用它,当满足条件时,SpinWaitSpinUntil()立即退出。因此,无论哪个先出现,“条件已满足”还是超时,任务都会退出。它不会继续运行。
柯蒂斯

-3

在wp8上:

包装:

Task GetCustomersSynchronously()
{
    Task t = new Task(async () =>
    {
        myCustomers = await GetCustomers();
    }
    t.RunSynchronously();
}

称它为:

GetCustomersSynchronously();

3
不,这不会起作用,因为任务不会等待构造函数的委托(它是委托,而不是任务。)
Rico Suter

-4
    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;
        }
    }

-5

或者您可以选择:

customerList = Task.Run<List<Customer>>(() => { return GetCustomers(); }).Result;

为此,请确保您引用扩展程序集:

System.Net.Http.Formatting

-9

尝试以下代码对我有用:

public async void TaskSearchOnTaskList (SearchModel searchModel)
{
    try
    {
        List<EventsTasksModel> taskSearchList = await Task.Run(
            () => MakeasyncSearchRequest(searchModel),
            cancelTaskSearchToken.Token);

        if (cancelTaskSearchToken.IsCancellationRequested
                || string.IsNullOrEmpty(rid_agendaview_search_eventsbox.Text))
        {
            return;
        }

        if (taskSearchList == null || taskSearchList[0].result == Constants.ZERO)
        {
            RunOnUiThread(() => {
                textViewNoMembers.Visibility = ViewStates.Visible;                  
                taskListView.Visibility = ViewStates.Gone;
            });

            taskSearchRecureList = null;

            return;
        }
        else
        {
            taskSearchRecureList = TaskFooterServiceLayer
                                       .GetRecurringEvent(taskSearchList);

            this.SetOnAdapter(taskSearchRecureList);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("ActivityTaskFooter -> TaskSearchOnTaskList:" + ex.Message);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.