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

How to call asynchronous method from synchronous method in C#?

提问人:Tower 提问时间:2/19/2012 最后编辑:Theodor ZouliasTower 更新时间:10/24/2023 访问量:1258512

问:

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

这甚至可能吗?

下面是从异步方法调用这些方法的一个示例: 演练:使用 Async 和 Await 访问 Web(C# 和 Visual Basic)

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

C# 异步 async-await 同步

评论

4赞 Timothy Lee Russell 8/5/2012
我也遇到了这个问题。重写 RoleProvider,无法更改 GetRolesForUser 方法的方法签名,因此无法使方法异步,因此无法使用 await 异步调用 api。我的临时解决方案是将同步方法添加到我的泛型 HttpClient 类中,但想知道这是否可行(以及可能产生的影响)。
3赞 Dai 5/7/2018
由于方法不返回 a,这意味着调用方无法知道它何时完成,因此它必须返回。async void Foo()TaskTask
1赞 noseratio 11/29/2018
链接有关如何在 UI 线程上执行此操作的相关问答
2赞 J.Tribbiani 5/6/2022
我已经使用了这种方法,并且似乎可以完成这项工作:MyMethodAsync.GetAwaiter()。GetResult();在此之前,您可能需要查看以下最终归结为死锁和线程池匮乏的文章:medium.com/rubrikkgroup/...
0赞 The incredible Jan 10/4/2022
@Timothy Lee Russell,我认为 GetRolesForUser() 不应该做太多事情。特别是不要调用耗时的异步方法。

答:

15赞 base2 2/19/2012 #1

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

正如很多人在这里建议的那样,您可以在同步方法中对生成的任务调用 或 Result,但最终在该方法中会出现阻塞调用,这在某种程度上违背了异步的目的。Wait()

如果你真的无法制作你的方法,并且你不想锁定同步方法,那么你将不得不使用回调方法,将其作为参数传递给任务中的方法。asyncContinueWith()

评论

11赞 Jeff Mercado 2/19/2012
那么现在就不会同步调用该方法了,不是吗?
6赞 base2 2/19/2012
据我了解,问题是你能从非异步方法调用异步方法吗?这并不意味着必须以阻塞方式调用异步方法。
2赞 Jeff Mercado 2/19/2012
对不起,你的“他们也必须被标记”把我的注意力从你真正在说什么上移开了。async
2赞 awe 3/11/2013
如果我真的不关心异步性,那么这样称呼它是否可以(以及 Stephen Cleary 一直在唠叨的包装异常中出现死锁的可能性呢?我有一些测试方法(必须同步执行)来测试异步方法。在继续之前,我必须等待结果,这样我才能测试异步方法的结果。
68赞 Despertar 2/19/2012 #2
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”关键字理解为“启动此长时间运行的任务,然后将控制权返回给调用方法”。完成长时间运行的任务后,它会执行该任务之后的代码。await 后面的代码类似于以前的 CallBack 方法。最大的区别是逻辑流不会中断,这使得写入和读取变得更加容易。

评论

24赞 Stephen Cleary 2/19/2012
Wait包装异常,并有可能发生死锁。
0赞 awe 3/11/2013
我以为如果你在不使用 的情况下调用异步方法,它将同步执行。至少这对我有用(无需打电话)。实际上,当我尝试调用时,我遇到了一个异常,因为它已经被执行了!awaitmyTask.WaitmyTask.RunSynchronously()
0赞 Eric J. 10/19/2013
注意:然后,您可以通过在 Wait() 之后调用 myTask.Result() 来获取 type 的结果T
3赞 iCollect.it Ltd 2/23/2015
截至今天,这个答案是否仍然有效?我刚刚在 MVC Razor 项目中尝试过它,该应用程序只是在访问 ..Result
8赞 Luaan 2/24/2015
@TrueBlueAussie 这是同步上下文死锁。异步代码会编组回同步上下文,但当时被调用阻止,因此它永远不会到达那里。而且永无止境,因为它在等待一个在等待结束的人,基本上是:DResultResultResult
1072赞 Stephen Cleary 2/19/2012 #3

异步编程确实通过代码库“增长”。它被比作僵尸病毒。最好的解决方案是让它增长,但有时这是不可能的。

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

解决方案 A

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

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

不想使用 或 因为它们将异常包装在 中。Task.WaitTask.ResultAggregateException

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

解决方案 B

如果确实需要同步回其上下文,则可以使用来提供嵌套上下文:MyAsyncMethodAsyncContext.RunTask

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

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

var result = AsyncContext.Run(MyAsyncMethod);

(在此示例中使用是可以的,因为会传播异常)。Task.ResultRunTaskTask

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

  1. 同步方法调用异步方法,获取 .Task
  2. 同步方法对 执行阻塞等待。Task
  3. 该方法不使用 .asyncawaitConfigureAwait
  4. 在这种情况下无法完成,因为它仅在方法完成时完成;该方法无法完成,因为它正在尝试将其延续计划到 ,并且 WinForms/WPF/SL/ASP.NET 将不允许继续运行,因为同步方法已在该上下文中运行。TaskasyncasyncSynchronizationContext

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

解决方案 C

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

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

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

更新:Stephen Cleary 撰写的 2015 年 MSDN 文章“异步编程 - 棕地异步开发”。

评论

22赞 deadlydog 7/26/2012
解决方案 A 似乎是我想要的,但它看起来像任务。WaitAndUnwrapException() 没有进入 .Net 4.5 RC;它只有任务。Wait() 中。知道如何使用新版本执行此操作吗?或者这是您编写的自定义扩展方法?
6赞 Stephen Cleary 7/26/2012
WaitAndUnwrapException是我自己的方法,来自我的 AsyncEx 库。官方的 .NET 库没有为混合同步和异步代码提供太多帮助(一般来说,你不应该这样做!我正在等待 .NET 4.5 RTW 和新的非 XP 笔记本电脑,然后再将 AsyncEx 更新为 4.5(我目前无法为 4.5 开发,因为我在 XP 上停留了几个星期)。
19赞 Stephen Cleary 6/23/2013
AsyncContext现在有一个接受 lambda 表达式的方法,因此您应该使用Runvar result = AsyncContext.Run(() => MyAsyncMethod());
4赞 Stephen Cleary 4/15/2014
@Asad:是的,2 年多后,API 发生了变化。您现在可以简单地说 var result = AsyncContext.Run(MyAsyncMethod);
10赞 Stephen Cleary 2/6/2020
@bluejayke:安装 Nito.AsyncEx 库。或者,使用 代替 ..GetAwaiter().GetResult().WaitAndUnwrapException()
76赞 NStuke 3/27/2014 #4

我不是 100% 确定,但我相信本博客中描述的技术在许多情况下都应该有效:

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

评论

12赞 orad 3/30/2017
上面 Stephen Cleary 的答案中的解决方案 A 使用了这种方法。请参阅 WaitAndUnwrapException 源。
0赞 Emil 7/5/2017
如果您调用的函数是 void 或 task,您是否需要使用 GetResult()?我的意思是,如果你不想得到任何结果
1赞 NStuke 7/11/2017
是的,否则在任务完成之前它不会阻止。或者,不要调用 GetAwaiter()。GetResult() 可以调用 。等待()
2赞 NStuke 3/29/2018
这就是“许多情况”的部分。这取决于整个线程模型以及其他线程正在执行的操作,以确定是否存在死锁风险。
0赞 nawfal 6/5/2020
GetAwaiter() 中。GetResult() 仍可能导致死锁。它只会将异常解包为更合理的异常。
311赞 Erik Philips 8/3/2014 #5

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

对于那些关心代码许可条款的人,这里有一个指向非常相似的代码的链接(只是在线程上添加了对区域性的支持),其中包含注释以指示它是由 Microsoft 许可的 MIT。https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

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

绝对不行。

简单的答案是

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

首先,关闭

Task.Result 是否与 相同。GetAwaiter.GetResult()?

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

这应该导致任何人问

这难道不和调用 Task.Run(async ()=> await AsyncFunc()) 一样。GetAwaiter() 中。GetResult()

然后,这将取决于

关于 Task.Start() 、 Task.Run() 和 Task.Factory.StartNew() 的用法

摘录:

Task.Run 使用 TaskCreationOptions.DenyChildAttach,这意味着子任务不能附加到父任务,它使用 TaskScheduler.Default,这意味着在线程池上运行任务的任务将始终用于运行任务

Task.Factory.StartNew 使用 TaskScheduler.Current,这意味着当前线程的调度程序,它可能是 TaskScheduler.Default,但并非总是如此。

补充阅读:

指定同步上下文

ASP.NET Core SynchronizationContext

为了额外的安全性,这样称呼它不是更好吗 这样我们告诉“内部”方法“请不要尝试同步到上层上下文和 dealock”AsyncHelper.RunSync(async () => await AsyncMethod().ConfigureAwait(false));

alex-from-jitbit 的一点非常好,正如大多数对象架构问题一样,这取决于

作为扩展方法,您是想对每个调用强制执行该方法,还是让使用该函数的程序员在他们自己的异步调用中配置它?我可以看到调用三个场景的用例;它很可能不是你在 WPF 中想要的东西,在大多数情况下当然是有道理的,但考虑到 ASP.Net Core 中没有上下文,如果你能保证它是 ASP.Net Core 的内部,那么这并不重要。

评论

4赞 Bob.at.Indigo.Health 5/22/2015
我的异步方法等待其他异步方法。我不会用 来装饰我的任何通话。我尝试使用 Global.asax 中的函数调用异步函数,它似乎有效。这是否意味着这确实不容易出现我在这篇文章的其他地方读到的“元组回到调用者的上下文”的僵局问题?awaitConfigureAwait(false)AsyncHelper.RunSyncApplication_Start()AsyncHelper.RunSync
1赞 Erik Philips 5/22/2015
@Bob.at.SBS 取决于您的代码功能。这并不像我使用此代码那么简单,我是否安全。这是同步运行异步命令的非常小且半安全的方法,它很容易被不当使用以导致死锁。
1赞 Bob.at.Indigo.Health 5/23/2015
谢谢。2 个后续问题:1) 您能否举例说明异步方法想要避免的会导致死锁的事情,以及 2) 在这种情况下,死锁通常与时间有关吗?如果它在实践中有效,我的代码中是否仍然潜伏着与时间相关的死锁?
1赞 LeonardoX 9/17/2017
@Bob.at...Erik 提供的代码在 Asp 下完美运行。net mvc5 和 EF6,但当我尝试任何其他解决方案时没有 (ConfigureAwait(false)。GetAwaiter() 中。GetResult() 或 .result),它完全挂起我的 Web 应用程序
1赞 Aidan 8/9/2020
这是唯一不会导致我的使用场景死锁的答案。
263赞 Lee Smith 2/24/2015 #6

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://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-topics-subscriptions

评论

22赞 Prisoner ZERO 6/28/2015
我不知道为什么有人投票否决了这个。这对我来说效果很好。如果没有这个修复程序,我将不得不到处传播 ASYCH。
21赞 crush 10/29/2015
为什么这比 ?MainAsync().Wait()
14赞 Hajjat 12/24/2015
我同意。你只需要 MainAsync()。Wait() 而不是所有这些。
14赞 David 2/4/2016
@crush我正在描述如何避免一些僵局。在某些情况下,调用 .来自 UI 或 asp.net 线程的 Wait() 会导致死锁。异步死锁
8赞 Chris Pratt 10/11/2017
@ClintB:你绝对不应该在 ASP.NET Core中这样做。Web 应用程序特别容易受到线程匮乏的影响,每次执行此操作时,您都会从池中提取一个线程,否则该线程将用于处理请求。对于桌面/移动应用程序来说,问题不大,因为它们传统上是单用户。
32赞 Robert J 8/25/2015 #7

但是,有一个很好的解决方案可以在(几乎:请参阅注释)每种情况下都有效:即席消息泵 (SynchronizationContext)。

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

临时消息泵帮助程序的代码:

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

有关异步泵的更详细说明,请点击此处

评论

0赞 PreguntonCojoneroCabrón 3/27/2018
异常上下文和 AsyncPump stackoverflow.com/questions/23161693/...
3赞 Josh Mouch 5/4/2019
这在 Asp.net 方案中不起作用,因为您可能会随机丢失 HttpContext.Current。
1赞 Erik Philips 9/9/2021
@JoshMouch 除非您使用的是非常旧版本的 asp.net,否则永远不应该使用 HttpContext.Current。
430赞 Tohid #8

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

首先阅读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;
    }
}

评论

9赞 Dan Esparza 8/18/2017
两年过去了,我很好奇这个解决方案是如何坚持下去的。有什么消息吗?这种方法是否有新手丢失的微妙之处?
53赞 Chris Pratt 10/11/2017
这不会死锁,这仅仅是因为它被迫在原始线程的同步上下文之外的新线程中运行。但是,在某些环境中,这是非常不明智的:尤其是 Web 应用程序。这可以有效地将 Web 服务器的可用线程减半(一个线程用于请求,一个线程用于此线程)。你做得越多,情况就越糟。您最终可能会死锁整个 Web 服务器。
63赞 Tohid 10/28/2017
@ChrisPratt - 你可能是对的,因为这不是异步代码中的最佳实践。但是,同样,最初问题的答案是什么?从不同步调用异步方法?我们希望,但在现实世界中,有时我们必须这样做。Task.Run()
57赞 Mass Dot Net 11/18/2020
有点疯狂的是,.NET 5.0 已经发布,仍然没有同步调用异步方法的万无一失的方法。
4赞 AsPas 9/3/2021
@JHBonarius 在第一种情况下,任务在后台线程上执行,结果由原始上下文通过属性获取。在第二种情况下,任务在同一上下文上执行,如果该上下文是 UI 线程,则会导致死锁。Result
16赞 rajesh A 6/21/2018 #9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

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

或者使用这个:

var result=result.GetAwaiter().GetResult().AccessToken
24赞 jrypkahauer 12/22/2018 #10

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

如果你往里看,有一个类叫做 .在该类中,您将看到静态扩展方法,它完全只是阻塞线程,直到任务返回。Microsoft.VisualStudio.Services.WebApiTaskExtensionsTask.SyncResult()

在内部,它调用这非常简单,但是它重载以处理返回或...句法糖,宝贝......爸爸爱吃甜食。task.GetAwaiter().GetResult()asyncTaskTask<T>Task<HttpResponseMessage>

它看起来像是在阻塞上下文中执行异步代码的 MS 官方方式。似乎对我的用例非常有效。...GetAwaiter().GetResult()

评论

6赞 Dawood ibn Kareem 8/8/2019
你让我处于“完全只是块”的状态。
3赞 Aidan 8/9/2020
任务。GetAwaiter() 中。GetResult() 总是为我导致死锁。
4赞 Jiří Herník 1/29/2020 #11

好吧,我多年来一直使用这种方法,它还可以处理和传播来自底层异步任务的异常。它完美无缺。

private string RunSync()
{
    var task = Task.Run(async () => await GenerateCodeService.GenerateCodeAsync());
    if (task.IsFaulted && task.Exception != null)
    {
        throw task.Exception;
    }

    return task.Result;
}

但是,由于Microsoft创建了这个异步帮助程序:https://github.com/aspnet/AspNetIdentity/blob/main/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

这也是他们的来源:

public static void RunSync(Func<Task> func)
        {
            var cultureUi = CultureInfo.CurrentUICulture;
            var culture = CultureInfo.CurrentCulture;
            _myTaskFactory.StartNew(() =>
            {
                Thread.CurrentThread.CurrentCulture = culture;
                Thread.CurrentThread.CurrentUICulture = cultureUi;
                return func();
            }).Unwrap().GetAwaiter().GetResult();
        }

评论

1赞 Per G 3/26/2020
适用于返回任务。GetAwaiter() 中。GetResult();
0赞 Per G 4/2/2020
.我认为结果与.GetAwaiter() 中。GetResult()
0赞 Jiří Herník 6/17/2020
好吧,事实并非如此,因为通常.结果不等待,可能会导致死锁。
2赞 Aidan 8/9/2020
对不起,这给我带来了死锁。AsyncHelper 的答案似乎是唯一一个没有答案的答案。
1赞 Kiquenet 12/9/2022
锁的源代码示例@Aidan?
9赞 Metalogic 8/6/2020 #12

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

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

评论

0赞 herdsothom 9/14/2021
我必须将方法更改为这样才能使其工作:return Task.Run(async () => await method()).GetAwaiter().GetResult();
0赞 537mfb 9/24/2021
@herdsothom这是因为在您的示例中,它实际上是一个异步方法本身 - 而在 @Metalogic 的例子中,他是一个异步调用的同步方法。在你的情况下,应该简单地提供method()foo()method().GetAwaiter().GetResult();
1赞 Metalogic 10/24/2023
@Theodor Zoulias,我已经按照建议更新了我的答案。
12赞 panpawel 9/20/2020 #13

这是最简单的解决方案。我在互联网上的某个地方看到了它,我不记得在哪里,但我已经成功地使用它了。它不会使调用线程死锁。

    void SynchronousFunction()
    {
        Task.Run(Foo).Wait();
    }

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

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

评论

0赞 Jiří Herník 2/28/2023
这不会重新引发原始异常,而是在与线程相关的内容上失败。在我的回复中解决了这个问题
11赞 Hubeyb Özkul 5/6/2022 #14

斯蒂芬·克利里(Stephen Cleary)的回答;

这种方法不应该导致死锁(假设 ProblemMethodAsync 不会向 UI 线程或任何内容发送更新 像那样)。它确实假设 ProblemMethodAsync 可以在 线程池线程,但情况并非总是如此。

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

这是方法;

线程池黑客 与阻塞黑客类似的方法是 将异步工作卸载到线程池,然后在 生成的任务。使用此 hack 的代码看起来像代码 如图 7 所示。

图 7 线程池黑客攻击的代码

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 元素或 HttpContext.Current ASP.NET。

评论

1赞 Ian GM 3/21/2023
谢谢。对我有用,可能是实现这一目标的最新方法。
0赞 Victor Irzak 11/21/2022 #15

现在,可以使用源生成器通过同步方法生成器库 (nuget) 创建方法的同步版本。

按如下方式使用它:

[Zomp.SyncMethodGenerator.CreateSyncVersion]
public async Task FooAsync()

这将生成可以同步调用的方法。Foo

-2赞 The incredible Jan 6/26/2023 #16

每个人似乎都以需要等待结果为前提。 我经常需要从我不关心结果的同步方法更新数据。我只是使用丢弃:

_ = UpdateAsync();

评论

0赞 Jiří Herník 7/27/2023
这是错误的,对不起,这意味着,不能保证,里面的代码会运行到最后。如果 main() 将达到执行的终点,无论它是否完成,它都不会照顾里面的代码,而只会关闭它。
0赞 The incredible Jan 8/15/2023
@JiříHerník我有一个项目,其中客户端中的数据列表(用户看到并用于选择现有记录的表)是异步加载的。如果代码在用户关闭应用程序之前完成,为什么这很重要?也许“等待结果”并不是最好的表达方式......
1赞 Jiří Herník 8/26/2023
未等待的异步任务中的代码未被隔离以完成。仅此而已。