【问题标题】:How would I run an async Task<T> method synchronously?我将如何同步运行异步 Task<T> 方法?
【发布时间】:2011-07-02 23:37:21
【问题描述】:

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

异步方法:

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

堆栈跟踪

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

标签: c# asynchronous c#-5.0 async-await


【解决方案1】:

为什么不创建这样的调用:

Service.GetCustomers();

这不是异步的。

【讨论】:

  • 如果我不能让它工作,我会这样做......除了异步版本之外,还要创建一个同步版本
  • 如果因为使用了库而无法编写自己的函数怎么办?
【解决方案2】:

在您的代码中,您的第一个 wait 任务执行,但您还没有启动它,所以它无限期地等待。试试这个:

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

编辑:

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

[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_actionnull

【讨论】:

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

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

可以调用:

customerList = AsyncHelpers.RunSync&lt;List&lt;Customer&gt;&gt;(() =&gt; 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 1Part 2Part 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
【解决方案4】:

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

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

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

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

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

.... 备份! ....

我要问一个问题,为什么你试图同步阻塞异步方法?这样做会破坏为什么要异步调用该方法的目的。通常,当您开始在 Dispatcher 或 UI 方法上使用 await 时,您会希望将整个 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 本身上执行。这是解决您的问题的一种解决方法。

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

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

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

【讨论】:

  • +1 以获得详细说明,但是TaskEx.RunEx(GetCustomers).Result 在应用程序在挂起的调度程序线程上运行时会挂起该应用程序。此外,GetCustomers() 方法通常是异步运行的,但是在一种情况下它需要同步运行,所以我一直在寻找一种方法来做到这一点,而无需构建该方法的同步版本。
  • +1 表示“你为什么要尝试同步阻塞异步方法?”总有办法正确使用async 方法;当然应该避免嵌套循环。
【解决方案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 的 cmets),这种方式不是线程安全的

【讨论】:

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

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

.Net 4.5

就用这个吧:

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

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

见: TaskAwaiter, Task.Result, Task.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 可能会导致死锁。
  • 同步运行对我有用...我不知道我是否遗漏了什么,但这似乎比标记答案的恐怖更可取-我只是在寻找一种关闭异步的方法用于测试阻止 ui 挂起的代码
【解决方案7】:

在 wp8 上:

包装一下:

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

叫它:

GetCustomersSynchronously();

【讨论】:

  • 不,这行不通,因为任务不会等待构造函数的委托(它是委托而不是任务..)
【解决方案8】:

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

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() 的结果可能是 TaskTask>,所以你正在等待一个快速完成的外部任务的结果,但是内部任务(由于在异步方法中等待长时间运行的作业)仍在运行。结论是我们可能需要使用 Unwrap() 方法(就像在@J.Lennon 帖子中所做的那样)来实现异步方法的同步行为。
  • @sgnsajgon 你错了。 Task.Run 与 Task.Factory.StartNew 的不同之处在于它已经自动解包结果。见this article
  • 我可以直接写Task.Run(DoSomethingAsync) 吗?这将删除一级代表。
  • 是的。但是,相反的方向,如Task&lt;MyResult&gt; task = Task.Run(async () =&gt; await DoSomethingAsync()); 更明确,并解决了@sgnsajgon 的担忧,即它可能会返回一个Task>。无论哪种方式都选择了正确的 Task.Run 重载,但异步委托使您的意图显而易见。
【解决方案9】:
    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;
        }
    }

【讨论】:

    【解决方案10】:

    我遇到过几次,主要是在单元测试或 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(() =&gt; ..).Wait()(稍作调整)。两者都应该工作。
    【解决方案11】:

    这对我来说很好用

    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,而是 Task>。在一些简单的场景中,由于 TaskScheduler 优化,您的解决方案可能会起作用,例如在 Wait 操作期间使用 TryExecuteTaskInline 方法在当前线程中执行任务。请查看我对 @987654321 的评论@回答。
    • 这是不正确的。 Task.Run 将返回 Task。看到这个重载msdn.microsoft.com/en-us/library/hh194918(v=vs.110).aspx
    • 这应该如何使用? WPF 中的这种死锁:MyAsyncMethod().RunTaskSynchronously();
    • 这仅适用于没有同步上下文的平台(控制台应用程序、ASP.NET Core 应用程序等)。对于具有同步上下文的平台,这仅适用于冷任务,也就是 99% 的正常情况。对于已经开始的任务,将其包装在Task.Run 中是没有意义的。换句话说,在像GetFromNetworkAsync().RunTaskSynchronously() 这样的正常用法中,UI 应用程序会挂起。
    【解决方案12】:

    此答案专为使用 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();
            }
        }
    }
    

    【讨论】:

      【解决方案13】:

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

      【讨论】:

      【解决方案14】:

      试试下面对我有用的代码:

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

      【讨论】:

        【解决方案15】:

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

        public Task<int> BlahAsync()
        {
            // ...
        }
        
        int result = BlahAsync().GetAwaiter().GetResult();
        

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

        • 它不会吞下异常(如Wait
        • 它不会包装AggregateException 中抛出的任何异常(如Result
        • 适用于TaskTask&lt;T&gt; (try it out yourself!)

        此外,由于 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(),“此方法适用于编译器用户,而不是直接在代码中使用。”
        【解决方案16】:

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

        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,你的代码帮助了我。
        【解决方案17】:

        或者你可以选择:

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

        要编译,请确保您引用扩展程序集:

        System.Net.Http.Formatting
        

        【讨论】:

          【解决方案18】:

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

          最好的答案是你不知道,具体取决于“情况”是什么。

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

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

          它是构造函数吗?然后你可能想考虑一个异步工厂方法。 (欲了解更多信息,请参阅我的blog post on asynchronous constructors)。

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

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

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

          在这种情况下,您需要使用我在brownfield async development 上的文章中描述的一种技巧,具体来说:

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

          嵌套消息循环是所有黑客攻击中最危险的,因为它会导致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) 我强烈反对这个公认的答案。因此,将其作为一个副本关闭将是滥用权力;关闭它作为这个(或合并)的副本将更加危险的答案。我顺其自然,把它留给社区。​​span>
          • 这个答案让我大吃一惊。 “一直使用异步” 是令人困惑的建议,因为显然无法遵循。带有 async Main() 方法的程序无法编译;在某些时候,您 必须 弥合同步和异步世界之间的差距。这不是非常罕见的情况”,实际上在每个调用异步方法的程序中都是必需的。没有不"do sync-over-async" 的选项,只有一个选项可以将这个负担分流到调用方法,而不是在你当前编写的方法中承担它。
          • @MarkAmery:在控制台应用程序的Main 方法中,异步同步是必需的。 ASP.NET、单元测试框架和每个 UI 系统都原生支持异步。即使您的所有应用程序都是控制台应用程序,您也只需为每个应用程序执行一次异步同步。 (当然,不支持异步的库回调可能需要额外的 hack)。
          • 太棒了。我现在要在我的应用程序中的所有方法上加上async。这很多。这不能只是默认设置吗?
          • 值得一提的是,正如您replied me 和基于您的博客here,死锁对于ASP.NET Core 来说不是问题!
          【解决方案19】:

          使用下面的代码片段

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

          【讨论】:

          • Task.WaitAll 在这里没有添加任何内容。为什么不等一下?
          【解决方案20】:

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

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

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

          【讨论】:

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

          正如许多人在 cmets 中所说,简单地调用 .Result;.Wait() 会带来死锁风险。由于我们大多数人都喜欢oneliners,您可以将它们用于.Net 4.5&lt;

          通过异步方法获取值:

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

          同步调用异步方法

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

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

          来源:

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

          【讨论】:

            【解决方案22】:

            这对我有用

            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】:

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

              对于返回Task的异步方法。

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

              对于返回Task&lt;T&gt;的异步方法

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

              编辑

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

              【讨论】:

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

              我发现 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() 立即退出。因此,无论是“条件满足”还是超时,任务都会退出。它不会继续运行。
              【解决方案25】:

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

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

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

              【讨论】:

                【解决方案26】:

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

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

                【讨论】:

                  猜你喜欢
                  • 2022-06-21
                  • 2023-03-26
                  相关资源
                  最近更新 更多