ChatGPT解决这个技术问题 Extra ChatGPT

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

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

异步方法:

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)

我还尝试了来自 here 的建议,但是当调度程序处于暂停状态时它不起作用。

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()
“如何同步调用异步方法”这个问题的最佳答案是“不要”。有 hacks 试图强制它工作,但它们都有非常微妙的陷阱。相反,备份并修复使您“需要”执行此操作的代码。
@Stephen Cleary 完全同意,但有时这是不可避免的,例如当您的代码依赖于不使用异步/等待的某些 3rd 方 API 时。此外,如果在使用 MVVM 时绑定到 WPF 属性,则实际上不可能使用 async/await,因为属性不支持此功能。
@StephenCleary 并非总是如此。我正在构建一个将在 GeneXus 中导入的 DLL。它不支持 async/await 关键字,所以我必须只使用同步方法。
@StephenCleary 1) GeneXus 是第 3 个 pt 工具,我无权访问它的源代码; 2)GeneXus 甚至没有“函数”的实现,所以我不知道如何用这种类型的东西实现“回调”。当然,这比同步使用 Task 更难; 3) 我正在将 GeneXus 与 MongoDB C# driver 集成,它仅异步公开一些方法
@StephenCleary这都是很好的理论,但是“不要这样做”有一个固有的问题,那就是它“不起作用”。 C# 主动禁止我在同步块中使用 await。我应该让 Microsoft 更改他们的语言吗?还是我应该放弃同步并接受混乱的数据结构? async 是癌症,而不是 GPL。一旦你拥有它,你就无法摆脱它。

R
Rachel

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

可以使用以下方法调用它:

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

代码来自 here

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

有关其工作原理的一些背景信息,Stephen Toub(Parallel 先生)为此撰写了一系列文章。 Part 1 Part 2 Part 3
我更新了 John 的代码,使其无需在 lambdas 中包装任务即可工作:github.com/tejacques/AsyncBridge。本质上,您使用 using 语句处理异步块。 using 块内的任何事情都是异步发生的,最后有一个等待。缺点是您需要自己在回调中解包任务,但它仍然相当优雅,特别是如果您需要一次调用多个异步函数。
@StephenCleary 尽管我通常同意您的观点,即代码应该一直异步,但有时您会发现自己处于一种不可行的情况,即 必须 将其强制为同步调用。基本上,我的情况是我所有的数据访问代码都是异步的。我需要基于站点地图构建站点地图,而我使用的第三方库是 MvcSitemap。现在,当通过 DynamicNodeProviderBase 基类对其进行扩展时,不能将其声明为 async 方法。要么我必须用一个新库替换,要么只调用一个同步操作。
@justin.lovell:是的,库限制可以迫使我们进行修改,至少在库更新之前是这样。听起来 MvcSitemap 是一种需要 hack 的情况(MVC 过滤器和子操作也是如此);我只是劝阻人们不要这样做,因为像这样的黑客在他们没有必要时被经常使用。特别是对于 MVC,一些 ASP.NET/MVC API 确实假设它们有一个 AspNetSynchronizationContext,所以如果您调用这些 API,这个特定的 hack 将不起作用。
此代码将不起作用。如果它是从池线程中调用的,它可能会触发线程饥饿死锁。您的调用者将阻塞等待操作完成,如果他用尽了线程池,这可能永远不会发生。请参阅this article
R
Rachel

请注意这个答案是三年前的。我主要根据使用 .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.RunSynchronously

.Net 4.0

用这个:

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

x.AsyncWaitHandle.WaitOne();

...或这个:

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

.Result 在某些情况下会产生死锁
Result 可以easily cause deadlock in async code,正如我在博客中所描述的那样。
@StephenCleary 我阅读了您的帖子,并亲自尝试过。老实说,我认为微软的某个人真的喝醉了......这与winforms和后台线程一样的问题......
问题涉及由异步方法返回的任务。此类任务可能已经启动、执行或取消,因此使用 Task.RunSynchronously 方法可能会导致 InvalidOperationException。请参阅 MSDN 页面:Task.RunSynchronously Method。此外,该任务可能是由 Task.Factory.StartNewTask.Run 方法(在异步方法中)创建的,因此再次尝试启动它很危险。运行时可能会出现一些竞争条件。另一方面,Task.WaitTask.Result 可能会导致死锁。
Run Synchronously 为我工作......我不知道我是否遗漏了什么,但这似乎比标记答案的恐怖更可取 - 我只是在寻找一种关闭异步以测试代码的方法,只是在那里停止挂起的ui
J
James Ko

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

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

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

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

它不会吞下异常(例如等待)

它不会包装在 AggregateException 中抛出的任何异常(如 Result)

适用于 Task 和 Task (你自己试试吧!)

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

编辑:请注意,这种方法(或使用 .Result)可能会死锁,除非您确保每次等待时都为所有可能达到的异步方法添加 .ConfigureAwait(false)BlahAsync() (不仅仅是它直接调用的)。 Explanation

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

为我工作简单的东西。此外,如果该方法返回 IAsyncOperation,我必须先将其转换为 Task:BlahAsync().AsTask().GetAwaiter().GetResult();
这导致了 asmx web 方法内部的死锁。尽管如此,将方法调用包装在 Task.Run() 中使其工作:Task.Run(() => BlahAsync()).GetAwaiter().GetResult()
我在语法上最喜欢这种方法,因为它不涉及 lambda。
请不要编辑其他人的答案以插入您自己的链接。如果您认为您的答案更好,请将其保留为评论。
docs.microsoft.com/en-us/dotnet/api/… 谈到 GetAwaiter(),“此方法适用于编译器用户,而不是直接在代码中使用。”
M
Michael L Perry

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

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; 

然后调用 task.Wait()。数据类型只是任务。
让我们假设 DoSomethingAsync() 整体上是长时间运行的异步方法(在内部它等待一个长时间运行的任务),但它很快将流控制返回给它的调用者,因此 lambda 参数的工作也很快结束。 Tusk.Run() 的结果可能是 Task 或 Task>,因此您正在等待快速完成的外部任务的结果,但内部任务(由于在异步方法中等待长时间运行的作业)仍在运行。结论是我们可能需要使用 Unwrap() 方法(就像在@J.Lennon 帖子中所做的那样)来实现异步方法的同步行为。
@sgnsajgon 你错了。 Task.Run 与 Task.Factory.StartNew 的不同之处在于它已经自动解包结果。请参阅this article
我可以只写 Task.Run(DoSomethingAsync) 吗?这将删除一级代表。
是的。但是,如 Task<MyResult> task = Task.Run(async () => await DoSomethingAsync()); 中的相反方向更明确,并解决了@sgnsajgon 的担忧,即它可能会返回一个任务<任务<MyResult>>。无论哪种方式都选择了正确的 Task.Run 重载,但异步委托使您的意图显而易见。
C
Community

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

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

它是属性获取器/设置器吗?在大多数情况下,拥有异步方法比“异步属性”更好。 (有关详细信息,请参阅 my blog post on asynchronous properties)。

这是一个 MVVM 应用程序并且您想要进行异步数据绑定吗?然后使用我的 NotifyTask 之类的东西,如我的 MSDN article on asynchronous data binding 中所述。

是构造函数吗?然后你可能想考虑一个异步工厂方法。 (有关详细信息,请参阅我的 blog post on asynchronous constructors)。

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

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

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

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

阻塞(例如,GetAwaiter().GetResult())。请注意,这可能会导致死锁(正如我在博客中所描述的那样)。

在线程池线程上运行代码(例如,Task.Run(..).GetAwaiter().GetResult())。请注意,这仅在异步代码可以在线程池线程上运行时才有效(即,不依赖于 UI 或 ASP.NET 上下文)。

嵌套消息循环。请注意,这仅在异步代码仅假定单线程上下文而不是特定上下文类型(许多 UI 和 ASP.NET 代码需要特定上下文)时才有效。

嵌套消息循环是所有 hack 中最危险的,因为它会导致 re-entrancy。重新进入非常难以推理,并且(IMO)是 Windows 上大多数应用程序错误的原因。特别是,如果您在 UI 线程上并且您在工作队列上阻塞(等待异步工作完成),那么 CLR 实际上会为您执行一些消息泵送 - 它实际上会处理一些 Win32 消息 从您的代码中。哦,你不知道哪些消息 - 当 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." 时,我们真的没有希望知道。

所以,当你在 UI 线程上这样阻塞时,你就是在自找麻烦。同一篇文章中的另一个 cbrumme 引用:“有时,公司内部或外部的客户会发现我们在 STA [UI 线程] 上的托管阻塞期间发送消息。这是一个合理的担忧,因为他们知道这非常困难编写面对重入的健壮代码。”

是的。 很难编写在可重入性面前稳健的代码。嵌套的消息循环强制您编写在重入面前稳健的代码。这就是 the accepted (and most-upvoted) answer for this question 在实践中极其危险的原因。

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

如果您确实遇到了这种情况,我建议您使用 Dispatcher.PushFrame for WPF apps 之类的东西,对于 WinForm 应用程序使用 Application.DoEvents 循环,对于一般情况,我自己的 AsyncContext.Run


@AlexeiLevenkov:我觉得这样做不对,有几个原因:1)链接问题的答案已经过时了。 2) 我写了一个entire article on the subject,我觉得它比任何现有的 SO Q/A 都更完整。 3) 这个问题的公认答案是非常受欢迎的。 4) 我强烈反对这个公认的答案。因此,将其作为一个副本关闭将是滥用权力;关闭它作为这个(或合并)的副本将更加危险的答案。我顺其自然,把它留给社区。
这个答案在我脑海中挥之不去。 “一直使用异步” 是令人困惑的建议,因为显然无法遵循。具有异步 Main() 方法的程序无法编译;在某些时候,您 必须 弥合同步和异步世界之间的差距。这不是非常罕见的情况”,实际上在每个调用异步方法的程序中都是必需的。没有不“do sync-over-async”的选项,只有一个选项可以将这个负担分流给调用方法,而不是在你当前正在编写的方法中承担它。
@MarkAmery:在控制台应用程序的 Main 方法中需要异步同步。 ASP.NET、单元测试框架和每个 UI 系统都原生支持异步。即使您的所有应用程序都是控制台应用程序,您也只需要为每个应用程序执行一次异步同步。 (当然,不支持异步的库回调可能需要额外的 hack)。
伟大的。我现在要在我的应用程序中的所有方法上加上 async。这很多。这不能只是默认值吗?
值得一提的是,作为您replied me,根据您的博客here,死锁对于 ASP.NET Core 来说不是问题!
M
Morse

如果我正确地阅读了您的问题-希望同步调用异步方法的代码正在暂停的调度程序线程上执行。并且您希望实际同步阻塞该线程,直到异步方法完成。

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

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

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

这就是您可能遇到问题的原因——异步方法实现试图在 Dispatcher 上运行其余的方法——即使它已暂停。

.... 备份! ……

我不得不问一个问题,为什么你试图同步阻塞异步方法?这样做会破坏为什么要异步调用该方法的目的。通常,当您开始在 Dispatcher 或 UI 方法上使用 await 时,您会希望将整个 UI 流异步化。例如,如果您的调用堆栈类似于以下内容:

[顶部] WebRequest.GetResponse() YourCode.HelperMethod() YourCode.AnotherMethod() YourCode.EventHandlerMethod() [UI 代码].Plumbing() - WPF 或 WinForms 代码 [消息循环] - WPF 或 WinForms 消息循环

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

[顶部] WebRequest.GetResponseAsync() YourCode.HelperMethodAsync() YourCode.AnotherMethodAsync() YourCode.EventHandlerMethodAsync() [UI 代码].Plumbing() - WPF 或 WinForms 代码 [消息循环] - WPF 或 WinForms 消息循环

实际回答

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

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

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

最终的 API 将是 Task.Run(...),但对于 CTP,您需要 Ex 后缀 (explanation here)。


+1 以获得详细解释,但是当应用程序在挂起的调度程序线程上运行时,TaskEx.RunEx(GetCustomers).Result 会挂起该应用程序。此外,GetCustomers() 方法通常是异步运行的,但是在一种情况下它需要同步运行,所以我一直在寻找一种方法来做到这一点,而无需构建该方法的同步版本。
+1 表示“您为什么要尝试同步阻止异步方法?”总有办法正确使用 async 方法;当然应该避免嵌套循环。
C
Clement

这对我来说效果很好

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.Run 创建),不适用于作为扩展方法参数传递的内部 await t 任务。您的 Task.Run 方法返回的不是 Task<T>,而是 Task<Task<T>>。在某些简单的情况下,您的解决方案可能会因为 TaskScheduler 优化而起作用,例如在 Wait 操作期间使用 TryExecuteTaskInline 方法在当前线程中执行任务。请查看我对 {1 的评论} 回答。
这是不正确的。 Task.Run 将返回 Task<T>。查看此重载 msdn.microsoft.com/en-us/library/hh194918(v=vs.110).aspx
这个应该怎么用? WPF 中的这种死锁:MyAsyncMethod().RunTaskSynchronously();
这仅适用于没有同步上下文的平台(控制台应用程序、ASP.NET Core 应用程序等)。对于具有同步上下文的平台,这仅适用于冷任务,也就是 99% 的正常情况。对于已经开始的任务,将其包装在 Task.Run 中是没有意义的。换句话说,在像 GetFromNetworkAsync().RunTaskSynchronously() 这样的正常用法中,UI 应用程序会挂起。
P
Palle Due

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

对于返回 Task 的异步方法。

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

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

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

编辑:

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


将近 8 年后我的答案 :) 第二个示例 - 将在主要使用的所有预定上下文中产生死锁(控制台应用程序/.NET 核心/桌面应用程序/...)。在这里,您可以了解我现在所说的更多概述:medium.com/rubrikkgroup/…
如果您想要同步调用,Result 非常适合这项工作,否则非常危险。名称 ResultResult 的智能感知中没有任何内容表明它是阻塞调用。真的应该改名了。
J
J. Lennon

我遇到过几次,主要是在单元测试或 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 我不知道它是什么,但这本质上是 Task.Run(() => ..).Wait() (稍作调整)。两者都应该工作。
w
wenhx

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

p
pixel

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

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

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


但是当异步代码返回我们需要的东西时,我们怎么能使用这个方法呢?
这适用于冷任务,而不是已经开始的任务。
a
animuson

请注意 - 这种方法:

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 的评论)


我解释了这个解决方案,检查编辑部分。
在异步情况下调用时,这很容易导致死锁。
@Servy 有意义。因此,当我正确使用 Wait(timeOut) 可以提供帮助,对吗?
然后你需要担心在操作实际上没有完成时达到超时,这非常糟糕,而且在它死锁的情况下等待超时所花费的时间(在这种情况下你仍在继续未完成时打开)。所以不,这并不能解决问题。
@Servy 看起来我必须为我的解决方案实施 CancellationToken
C
Cody Gray

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

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

编辑:

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

[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,您描述的异常发生在 m_actionnull 时。这有点奇怪,但我不是异步 CTP 方面的专家。正如我所说,您应该反编译您的代码并查看 Task 是如何被实例化的,以及它的 m_action 是如何成为 null 的。


我调整了我的问题,使我尝试过的代码更清晰一些。 RunSynchronously 返回错误 RunSynchronously may not be called on a task unbound to a delegate。谷歌没有帮助,因为所有结果都是中文的......
我认为不同之处在于我不创建任务然后尝试运行它。相反,当使用 await 关键字时,任务由 async 方法创建。我之前的评论中发布的例外是我得到的例外,尽管它是少数几个我无法谷歌并找到原因或解决方案的例外之一。
asyncasync 关键字只不过是语法糖。编译器生成代码以在 GetCustomers() 中创建 Task<Customer>,这就是我首先要看的地方。至于异常,您只发布了异常消息,没有异常类型和堆栈跟踪是没有用的。调用异常的 ToString() 方法并在问题中发布输出。
@gaearon:我在原始问题中发布了异常详细信息和堆栈跟踪。
@gaearon 我认为您之所以投反对票,是因为您的帖子不适用于问题。讨论的是异步等待方法,而不是简单的任务返回方法。此外,在我看来,async-await 机制是一种语法糖,但并不是那么微不足道 - 有 continuation 、上下文捕获、本地上下文恢复、增强的本地异常处理等等。然后,您不应在异步方法的结果上调用 RunSynchronously 方法,因为根据定义,异步方法应返回当前至少已调度的任务,并且不止一次处于运行状态。
D
Daniel A. White

为什么不创建如下调用:

Service.GetCustomers();

那不是异步的。


如果我无法使其正常工作,那将是我要做的...除了异步版本之外,还创建一个同步版本
如果因为使用库而无法编写自己的函数怎么办?
J
Jaider

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

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

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

M
Mahesh

使用下面的代码片段

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

Task.WaitAll 在这里没有添加任何内容。为什么不只是等待?
O
Ogglas

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

通过异步方法获取值:

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

同步调用异步方法

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

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

资源:

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


C
Contango

此答案专为使用 WPF for .NET 4.5 的任何人设计。

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

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

在不可避免的情况下,此类可以充当 async/await 世界和非 async/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();
        }
    }
}

d
donttellya

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

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

这不是真正的“同步”。您创建两个线程并等待其他线程的第一个结果。
除了所有的事情,这是一个非常糟糕的主意。
我刚刚编写了几乎相同的代码(逐行相同),但使用 SemaphoreSlim 而不是自动重置事件。真希望我早点看到这个。我发现这种方法可以防止死锁并使您的异步代码与真正的异步场景中的运行方式相同。不太确定为什么这是一个坏主意。似乎比我在上面看到的其他方法要干净得多。
@DanPantry 我现在实际上看到了一些我不理解的死锁。您能否详细说明为什么这是一个坏主意?
我的错。我有。现在这个工作。我的问题是我在主线程上创建任务,然后将该任务传递给调用异步方法。谢谢@donttellya,您的代码帮助了我。
D
Dan Nguyen

这对我有用

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

C
Curtis

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


这是轮询(旋转),委托将从池中获取线程,每秒最多 1000 次。任务完成后它可能不会立即返回控制权(最多 10+ms 个错误)。如果超时完成,任务将继续运行,这使得超时实际上毫无用处。
实际上,我在代码中到处使用它,当满足条件时,SpinWaitSpinUntil() 立即退出。因此,无论是“条件满足”还是超时,任务都会退出。它不会继续运行。
J
Jhollman

我知道这是一个老问题,但我想分享我的解决方案,可能不是最好的,但可以完成工作:

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

可能不是最好的——那为什么要发布呢?仅在旧问题明显改善所有现有答案时才发布新答案。
u
user2113284

在 wp8 上:

把它包起来:

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

叫它:

GetCustomersSynchronously();

不,这行不通,因为任务不会等待构造函数的委托(它是委托而不是任务..)
k
ksemenenko
    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;
        }
    }

t
thor

或者你可以选择:

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

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

System.Net.Http.Formatting

M
Massimiliano Kraus

尝试以下对我有用的代码:

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

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅