ChatGPT解决这个技术问题 Extra ChatGPT

如何从 C# 中的同步方法调用异步方法?

我有一个要从同步方法调用的 public async void Foo() 方法。到目前为止,我从 MSDN 文档中看到的只是通过异步方法调用异步方法,但我的整个程序不是用异步方法构建的。

这甚至可能吗?

下面是从异步方法调用这些方法的一个示例:
Walkthrough: Accessing the Web by Using Async and Await (C# and Visual Basic)

现在我正在研究从同步方法中调用这些异步方法。

我也遇到了这个。覆盖 RoleProvider 您无法更改 GetRolesForUser 方法的方法签名,因此您无法使该方法异步,因此无法使用 await 异步调用 api。我的临时解决方案是向我的通用 HttpClient 类添加同步方法,但想知道这是否可能(以及可能的含义)。
因为您的 async void Foo() 方法不返回 Task,这意味着调用者无法知道它何时完成,它必须返回 Task
链接 related q/a,了解如何在 UI 线程上执行此操作。
我使用过这种方法,似乎可以完成这项工作:MyMethodAsync.GetAwaiter().GetResult();在此之前,您可能需要查看以下最终归结为死锁和线程池饥饿的文章:medium.com/rubrikkgroup/…

T
Tyzoid

异步编程确实通过代码库“增长”。已经是 compared to a zombie virus。最好的解决方案是让它成长,但有时这是不可能的。

我在我的 Nito.AsyncEx 库中编写了一些类型来处理部分异步代码库。但是,没有适用于所有情况的解决方案。

解决方案 A

如果您有一个不需要同步回其上下文的简单异步方法,则可以使用 Task.WaitAndUnwrapException

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

您确实想使用 Task.WaitTask.Result,因为它们将异常包装在 AggregateException 中。

仅当 MyAsyncMethod 不同步回其上下文时,此解决方案才适用。换句话说,MyAsyncMethod 中的每个 await 都应该以 ConfigureAwait(false) 结尾。这意味着它不能更新任何 UI 元素或访问 ASP.NET 请求上下文。

解决方案 B

如果 MyAsyncMethod 确实需要同步回其上下文,那么您可以使用 AsyncContext.RunTask 来提供嵌套上下文:

var result = AsyncContext.RunTask(MyAsyncMethod).Result;

*2014 年 4 月 14 日更新:在更新版本的库中,API 如下:

var result = AsyncContext.Run(MyAsyncMethod);

(在此示例中可以使用 Task.Result,因为 RunTask 将传播 Task 异常)。

您可能需要 AsyncContext.RunTask 而不是 Task.WaitAndUnwrapException 的原因是在 WinForms/WPF/SL/ASP.NET 上发生了相当微妙的死锁可能性:

同步方法调用异步方法,获得一个任务。同步方法对 Task 进行阻塞等待。 async 方法使用 await 而不使用 ConfigureAwait。 Task 在这种情况下无法完成,因为它只有在 async 方法完成时才会完成;异步方法无法完成,因为它正在尝试将其继续调度到 SynchronizationContext,并且 WinForms/WPF/SL/ASP.NET 将不允许继续运行,因为同步方法已经在该上下文中运行。

这就是为什么在每个 async 方法中尽可能使用 ConfigureAwait(false) 是个好主意的原因之一。

解决方案 C

AsyncContext.RunTask 并非在所有情况下都有效。例如,如果 async 方法等待需要 UI 事件完成的操作,那么即使使用嵌套上下文,您也会死锁。在这种情况下,您可以在线程池上启动 async 方法:

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

但是,此解决方案需要在线程池上下文中工作的 MyAsyncMethod。因此它不能更新 UI 元素或访问 ASP.NET 请求上下文。在这种情况下,您不妨将 ConfigureAwait(false) 添加到其 await 语句中,并使用解决方案 A。

2019 年 5 月 1 日更新:当前的“最坏做法”在 MSDN article here 中。


解决方案 A 似乎是我想要的,但看起来 task.WaitAndUnwrapException() 没有进入 .Net 4.5 RC;它只有 task.Wait()。知道如何使用新版本执行此操作吗?或者这是您编写的自定义扩展方法?
WaitAndUnwrapException 是我自己的 AsyncEx library 方法。官方的 .NET 库没有为混合同步和异步代码提供太多帮助(通常,您不应该这样做!)。在更新 AsyncEx 以在 4.5 上运行之前,我正在等待 .NET 4.5 RTW 和一台新的非 XP 笔记本电脑(我目前无法为 4.5 开发,因为我在 XP 上停留了几个星期)。
AsyncContext 现在有一个采用 lambda 表达式的 Run 方法,因此您应该使用 var result = AsyncContext.Run(() => MyAsyncMethod());
@Asad:是的,两年多后 API 发生了变化。 You can now simply say var result = AsyncContext.Run(MyAsyncMethod);
@bluejayke:安装 Nito.AsyncEx 库。或者,使用 .GetAwaiter().GetResult() 而不是 .WaitAndUnwrapException()
T
Tohid

添加一个最终解决我的问题的解决方案,希望能节省一些人的时间。

首先阅读Stephen Cleary的几篇文章:

异步和等待

不要阻塞异步代码

从“不要阻塞异步代码”中的“两个最佳实践”中,第一个对我不起作用,第二个不适用(基本上如果我可以使用 await,我可以!)。

所以这是我的解决方法:将调用包装在 Task.Run<>(async () => await FunctionAsync()); 中,希望不再出现死锁

这是我的代码:

public class LogReader
{
    ILogger _logger;

    public LogReader(ILogger logger)
    {
        _logger = logger;
    }

    public LogEntity GetLog()
    {
        Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
        return task.Result;
    }

    public async Task<LogEntity> GetLogAsync()
    {
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    }
}

两年过去了,我很想知道这个解决方案的效果如何。任何新闻?这种方法在新手中是否存在微妙之处?
这不会死锁,真的,只是因为它被迫在一个新线程中运行,在原始线程的同步上下文之外。但是,在某些环境中这是非常不明智的:尤其是 Web 应用程序。这可以有效地将 Web 服务器的可用线程减半(一个线程用于请求,一个线程用于此)。你做的越多,情况就越糟。您可能最终导致整个 Web 服务器死锁。
@ChrisPratt - 你可能是对的,因为 Task.Run() 不是异步代码中的最佳实践。但是,再一次,原始问题的答案是什么?永远不要同步调用异步方法?我们希望,但在现实世界中,有时我们必须这样做。
@Tohid 你可以试试 Stephen Cleary 的图书馆。我已经看到人们假设这一点,Parallel.ForEach滥用不会对“现实世界”产生影响,最终导致服务器瘫痪。此代码适用于控制台应用程序,但正如 @ChrisPratt 所说,不应在 Web 应用程序中使用。它可能“现在”工作,但不可扩展。
有点疯狂的是 .NET 5.0 已经发布,并且仍然没有同步调用异步方法的防弹方法。
Z
Zoe stands with Ukraine

Microsoft 构建了一个 AsyncHelper(内部)类来将 Async 作为 Sync 运行。源代码如下:

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

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

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

Microsoft.AspNet.Identity 基类只有 Async 方法,为了将它们称为 Sync,有一些类的扩展方法看起来像(示例用法):

public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}

public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}

对于那些关心代码许可条款的人,这里有一个非常相似的代码的链接(只是在线程上添加了对文化的支持),其中有注释表明它是由微软授权的 MIT 许可。 https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

这和调用 Task.Run(async ()=> await AsyncFunc()).Result 不一样吗? AFAIK,微软现在不鼓励调用 TaskFactory.StartNew,因为它们都是等价的,而且一个比另一个更具可读性。

绝对不。

简单的答案是

.Unwrap().GetAwaiter().GetResult() != .Result

首先关闭

Is Task.Result the same as .GetAwaiter.GetResult()?

其次 .Unwrap() 导致任务的设置不阻止包装的任务。

这应该导致任何人问

这与调用 Task.Run(async ()=> await AsyncFunc()).GetAwaiter().GetResult() 不一样吗

那么这将是一个It Depends。

Regarding usage of Task.Start() , Task.Run() and Task.Factory.StartNew()

摘抄:

Task.Run 使用 TaskCreationOptions.DenyChildAttach ,这意味着子任务不能附加到父任务,它使用 TaskScheduler.Default ,这意味着在线程池上运行任务的任务将始终用于运行任务。 Task.Factory.StartNew 使用 TaskScheduler.Current 表示当前线程的调度程序,它可能是 TaskScheduler.Default 但并非总是如此。

补充阅读:

Specifying a synchronization context

ASP.NET Core SynchronizationContext

为了额外的安全,最好这样调用它 AsyncHelper.RunSync(async () => await AsyncMethod().ConfigureAwait(false));这样我们就告诉“内部”方法“请不要尝试同步到上层上下文并解除锁定”

真的很重要,因为大多数对象架构问题都取决于它。

作为一种扩展方法,您是希望 absolutely 每次调用都强制这样做,还是让使用该函数的程序员在他们自己的异步调用中配置它?我可以看到调用三个场景的用例;它很可能不是您在 WPF 中想要的东西,在大多数情况下当然是有道理的,但是考虑到没有 Context in ASP.Net Core,如果您可以保证它是 ASP.Net Core 的内部,那么它就没有关系了。


我的异步方法等待其他异步方法。我不使用 ConfigureAwait(false) 装饰我的任何 await 调用。我尝试使用 AsyncHelper.RunSync 从 Global.asax 中的 Application_Start() 函数调用异步函数,它似乎工作。这是否意味着 AsyncHelper.RunSync 可靠地不容易出现我在这篇帖子的其他地方读到的“封送回调用者的上下文”死锁问题?
@Bob.at.SBS 取决于你的代码做什么。这不像我使用这段代码那么简单,我是否安全。这是同步运行异步命令的最小且半安全的方式,它很容易被不当使用而导致死锁。
谢谢。 2个后续问题:1)您能否举一个异步方法希望避免导致死锁的示例,以及2)在这种情况下,死锁通常与时间相关吗?如果它在实践中有效,我的代码中是否仍然潜伏着与时间相关的死锁?
@Bob.at ... Erik 提供的代码在 Asp 下完美运行。 net mvc5 和 EF6,但当我尝试任何其他解决方案(ConfigureAwait(false).GetAwaiter().GetResult() 或 .result)时,我的 Web 应用程序完全挂起
对于我的使用场景,这是唯一不会导致死锁的答案。
L
Lee Smith

async Main 现在是 C# 7.2 的一部分,可以在项目高级构建设置中启用。

对于 C# < 7.2,正确的方法是:

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

您会在很多 Microsoft 文档中看到这一点,例如:https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-topics-subscriptions


我不知道为什么有人投了反对票。这对我很有用。如果没有这个修复,我将不得不到处传播 ASYCH。
为什么这比 MainAsync().Wait() 好?
我同意。您只需要 MainAsync().Wait() 而不是所有这些。
@crush我在描述这如何避免一些死锁。在某些情况下,从 UI 或 asp.net 线程调用 .Wait() 会导致死锁。 async deadlocks
@ClintB:您绝对不应该在 ASP.NET Core 中这样做。 Web 应用程序特别容易出现线程不足的情况,每次执行此操作时,都会从池中拉出一个线程,否则该线程将用于处理请求。桌面/移动应用程序的问题较少,因为它们传统上是单用户的。
P
Pang

我不能 100% 确定,但我相信 this blog 中描述的技术应该适用于许多情况:

因此,如果您想直接调用此传播逻辑,则可以使用 task.GetAwaiter().GetResult()。


上面 Stephen Cleary 的answer 中的解决方案 A 使用了这种方法。请参阅 WaitAndUnwrapException 来源。
如果您调用的函数是 void 或 task,您是否需要使用 GetResult()?我的意思是如果你不想得到任何结果
是的,否则它不会阻塞,直到任务完成。或者,您可以调用 .Wait() 而不是调用 GetAwaiter().GetResult()
这就是“许多情况”部分。这取决于整体线程模型以及其他线程正在做什么来确定是否存在死锁风险。
GetAwaiter().GetResult() 仍然会导致死锁。它只会将异常展开为更明智的异常。
D
Despertar
public async Task<string> StartMyTask()
{
    await Foo()
    // code to execute once foo is done
}

static void Main()
{
     var myTask = StartMyTask(); // call your method which will return control once it hits await
     // now you can continue executing code here
     string result = myTask.Result; // wait for the task to complete to continue
     // use result

}

您将“等待”关键字读作“启动这个长时间运行的任务,然后将控制权返回给调用方法”。一旦长时间运行的任务完成,它就会执行它之后的代码。 await 之后的代码类似于以前的 CallBack 方法。最大的区别是逻辑流程没有中断,这使得编写和阅读变得更加容易。


Wait 包装异常并有可能出现死锁。
我想如果你在不使用 await 的情况下调用异步方法,它将同步执行。至少这对我有用(无需调用 myTask.Wait)。实际上,当我尝试调用 myTask.RunSynchronously() 时出现异常,因为它已经被执行了!
我喜欢这个答案。很好的编辑评论,小而优雅。感谢您的贡献!我还在学习并发,所以一切都有帮助:)
到今天为止,这个答案是否仍然有效?我刚刚在 MVC Razor 项目中尝试过,应用程序在访问 .Result 时挂起。
@TrueBlueAussie 那是同步上下文死锁。您的异步代码编组回同步上下文,但当时被 Result 调用阻止,因此它永远不会到达那里。而且 Result 永远不会结束,因为它正在等待等待 Result 结束的人,基本上:D
R
Robert J

然而,有一个很好的解决方案(几乎:见评论)在每一种情况下都有效:即席消息泵(SynchronizationContext)。

调用线程将按预期被阻塞,同时仍确保从异步函数调用的所有延续不会死锁,因为它们将被编组到调用线程上运行的临时 SynchronizationContext(消息泵)。

ad-hoc 消息泵助手的代码:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Threading
{
    /// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
    public static class AsyncPump
    {
        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Action asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(true);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function
                syncCtx.OperationStarted();
                asyncMethod();
                syncCtx.OperationCompleted();

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Func<Task> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static T Run<T>(Func<Task<T>> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                return t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
        private sealed class SingleThreadSynchronizationContext : SynchronizationContext
        {
            /// <summary>The queue of work items.</summary>
            private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
                new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
            /// <summary>The processing thread.</summary>
            private readonly Thread m_thread = Thread.CurrentThread;
            /// <summary>The number of outstanding operations.</summary>
            private int m_operationCount = 0;
            /// <summary>Whether to track operations m_operationCount.</summary>
            private readonly bool m_trackOperations;

            /// <summary>Initializes the context.</summary>
            /// <param name="trackOperations">Whether to track operation count.</param>
            internal SingleThreadSynchronizationContext(bool trackOperations)
            {
                m_trackOperations = trackOperations;
            }

            /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
            /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
            /// <param name="state">The object passed to the delegate.</param>
            public override void Post(SendOrPostCallback d, object state)
            {
                if (d == null) throw new ArgumentNullException("d");
                m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
            }

            /// <summary>Not supported.</summary>
            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotSupportedException("Synchronously sending is not supported.");
            }

            /// <summary>Runs an loop to process all queued work items.</summary>
            public void RunOnCurrentThread()
            {
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            }

            /// <summary>Notifies the context that no more work will arrive.</summary>
            public void Complete() { m_queue.CompleteAdding(); }

            /// <summary>Invoked when an async operation is started.</summary>
            public override void OperationStarted()
            {
                if (m_trackOperations)
                    Interlocked.Increment(ref m_operationCount);
            }

            /// <summary>Invoked when an async operation is completed.</summary>
            public override void OperationCompleted()
            {
                if (m_trackOperations &&
                    Interlocked.Decrement(ref m_operationCount) == 0)
                    Complete();
            }
        }
    }
}

用法:

AsyncPump.Run(() => FooAsync(...));

here 提供了有关异步泵的更详细说明。


异常上下文和 AsyncPump stackoverflow.com/questions/23161693/…
这在 Asp.net 方案中不起作用,因为您可能会随机丢失 HttpContext.Current。
@JoshMouch 除非您使用的是非常旧的 asp.net 版本,否则您永远不应该使用 HttpContext.Current。
j
jrypkahauer

对于任何关注这个问题的人......

如果您查看 Microsoft.VisualStudio.Services.WebApi,则有一个名为 TaskExtensions 的类。在该类中,您将看到静态扩展方法 Task.SyncResult(),它完全阻塞线程直到任务返回。

它在内部调用 task.GetAwaiter().GetResult(),这很简单,但是它重载以处理任何返回 TaskTask<T>Task<HttpResponseMessage>async 方法......语法糖,宝贝......爸爸爱吃甜食.

看起来 ...GetAwaiter().GetResult() 是 MS 官方在阻塞上下文中执行异步代码的方式。对我的用例来说似乎工作得很好。


你让我“就像完全一样”。
task.GetAwaiter().GetResult() 总是对我造成死锁。
A
Andrew Myers
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

或者使用这个:

var result=result.GetAwaiter().GetResult().AccessToken

P
Paul

您可以从同步代码中调用任何异步方法,也就是说,直到您需要对它们进行 await,在这种情况下,它们也必须标记为 async

正如很多人在这里建议的那样,您可以在同步方法中对生成的任务调用 Wait() 或 Result,但最终会在该方法中进行阻塞调用,这有点违背异步的目的。

如果你真的不能让你的方法 async 并且你不想锁定同步方法,那么你将不得不通过将回调方法作为参数传递给任务上的 ContinueWith() 方法来使用回调方法.


那现在就不会同步调用该方法了吗?
据我了解,问题是您能否从非异步方法调用异步方法。这并不意味着必须以阻塞方式调用异步方法。
抱歉,您的“它们也必须标记为 async”使我的注意力从您的真正意思上移开。
如果我真的不关心异步性,那么这样调用它是否可以(以及 Stephen Cleary 一直在唠叨的包装异常中死锁的可能性呢?)我有一些测试方法(必须同步执行)测试异步方法。在继续之前我必须等待结果,所以我可以测试异步方法的结果。
M
Metalogic

受其他一些答案的启发,我创建了以下简单的辅助方法:

public static TResult RunSync<TResult>(Func<Task<TResult>> method)
{
    var task = method();
    return task.GetAwaiter().GetResult();
}

public static void RunSync(Func<Task> method)
{
    var task = method();
    task.GetAwaiter().GetResult();
}

它们可以按如下方式调用(取决于您是否返回值):

RunSync(() => Foo());
var result = RunSync(() => FooWithResult());

请注意,原始问题 public async void Foo() 中的签名不正确。它应该是 public async Task Foo(),因为对于不返回值的异步方法,您应该返回 Task not void(是的,有一些罕见的例外)。


我必须将方法更改为这样才能正常工作:return Task.Run(async () => await method()).GetAwaiter().GetResult();
@herdsothom 那是因为在您的情况下 method() 实际上是一个异步方法本身 - 而在@Metalogic 的示例中 foo() 是他异步调用的同步方法。在您的情况下,只需 method().GetAwaiter().GetResult(); 就足够了
p
panpawel

这是最简单的解决方案。我在网上某处看到过,不记得在哪里,但一直在成功使用。它不会死锁调用线程。

    void Synchronous Function()
    {
        Task.Run(Foo).Wait();
    }

    string SynchronousFunctionReturnsString()
    {
        return Task.Run(Foo).Result;
    }

    string SynchronousFunctionReturnsStringWithParam(int id)
    {
        return Task.Run(() => Foo(id)).Result;
    }

J
Jiří Herník

经过几个小时尝试不同的方法,或多或少的成功,这就是我的结局。它在获取结果时不会以死锁结束,它还会获取并抛出原始异常,而不是包装的异常。

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}

与返回任务一起使用。GetAwaiter().GetResult();
.Result 我认为与 .GetAwaiter().GetResult() 基本相同
好吧,事实并非如此,因为通常的 .Result 没有等待它,可能会导致死锁。
这会导致我陷入僵局,抱歉。 AsyncHelper 答案似乎是唯一没有的答案。
H
Hubeyb Özkul

斯蒂芬·克利里的回答;

这种方法不应该导致死锁(假设 ProblemMethodAsync 不向 UI 线程或类似的东西发送更新)。它确实假设 ProblemMethodAsync 可以在线程池线程上调用,但情况并非总是如此。

https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

这是方法;

线程池黑客 与阻塞黑客类似的方法是将异步工作卸载到线程池,然后阻塞生成的任务。使用此 hack 的代码将类似于图 7 中所示的代码。 图 7 线程池 Hack C# 的代码

public sealed class WebDataService : IDataService
{
  public string Get(int id)
  {
    return Task.Run(() => GetAsync(id)).GetAwaiter().GetResult();
  }
  public async Task<string> GetAsync(int id)
  {
    using (var client = new WebClient())
      return await client.DownloadStringTaskAsync(
      "https://www.example.com/api/values/" + id);
  }
}

对 Task.Run 的调用在线程池线程上执行异步方法。在这里它将在没有上下文的情况下运行,从而避免了死锁。这种方法的问题之一是异步方法不能依赖于在特定上下文中执行。因此,它不能使用 UI 元素或 ASP.NET HttpContext.Current。


F
Foxy

这些 Windows 异步方法有一个漂亮的小方法,称为 AsTask()。您可以使用它让方法将自身作为任务返回,以便您可以手动调用 Wait() 。

例如,在 Windows Phone 8 Silverlight 应用程序上,您可以执行以下操作:

private void DeleteSynchronous(string path)
{
    StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
    t.Wait();
}

private void FunctionThatNeedsToBeSynchronous()
{
    // Do some work here
    // ....

    // Delete something in storage synchronously
    DeleteSynchronous("pathGoesHere");

    // Do other work here 
    // .....
}

希望这可以帮助!


s
smj

如果你想运行它同步

MethodAsync().RunSynchronously()

此方法用于启动冷任务。通常异步方法返回一个热任务,换句话说,一个已经开始的任务。对热任务调用 RunSynchronously() 会导致 InvalidOperationException。试试这个代码:Task.Run(() => {}).RunSynchronously();