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

Tow*_*wer 757 c# async-await

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

这甚至可能吗?

以下是从异步方法调用这些方法的一个示例:http://msdn.microsoft.com/en-us/library/hh300224(v = vs.110).aspx

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

Ste*_*ary 635

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

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

解决方案A.

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

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Run Code Online (Sandbox Code Playgroud)

希望使用Task.WaitTask.Result因为包装在异常AggregateException.

只有在MyAsyncMethod不同步回其上下文时,此解决方案才适用.换句话说,每一个awaitMyAsyncMethod应该结束ConfigureAwait(false).这意味着它无法更新任何UI元素或访问ASP.NET请求上下文.

解决方案B.

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

var result = AsyncContext.RunTask(MyAsyncMethod).Result;
Run Code Online (Sandbox Code Playgroud)

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

var result = AsyncContext.Run(MyAsyncMethod);
Run Code Online (Sandbox Code Playgroud)

(Task.Result在这个例子中可以使用,因为它RunTask会传播Task异常).

你可能需要的原因AsyncContext.RunTask,而不是Task.WaitAndUnwrapException是因为对发生的WinForms/WPF/SL/ASP.NET一个相当微妙的僵局可能性:

  1. 同步方法调用异步方法,获取a Task.
  2. 同步方法执行阻塞等待Task.
  3. async方法使用awaitConfigureAwait.
  4. Task无法完成在这种情况下,因为它只有在完成async方法完成; 该async方法无法完成,因为它正在尝试将其继续安排到SynchronizationContext,并且WinForms/WPF/SL/ASP.NET将不允许继续运行,因为同步方法已在该上下文中运行.

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

解决方案C.

AsyncContext.RunTask不会在每个场景中都有效.例如,如果async方法等待需要UI事件完成的内容,那么即使使用嵌套上下文也会出现死锁.在这种情况下,您可以async在线程池上启动该方法:

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
Run Code Online (Sandbox Code Playgroud)

但是,此解决方案需要一个MyAsyncMethod可在线程池上下文中工作的解决方案.因此它无法更新UI元素或访问ASP.NET请求上下文.在这种情况下,您也可以添加ConfigureAwait(false)await语句,并使用解决方案A.

  • `AsyncContext`现在有一个带有lambda表达式的`Run`方法,所以你应该使用`var result = AsyncContext.Run(()=> MyAsyncMethod());` (9认同)
  • 解决方案A看起来像我想要的,但它看起来像task.WaitAndUnwrapException()没有进入.Net 4.5 RC; 它只有task.Wait().知道怎么用新版本做到这一点?或者这是你写的自定义扩展方法? (5认同)
  • @Asad:是的,两年多以后,API已经发生了变化.[你现在可以简单地说`var result = AsyncContext.Run(MyAsyncMethod);`](http://nitoasyncex.codeplex.com/wikipage?title=AsyncContext) (3认同)
  • “ WaitAndUnwrapException”是我自己的[AsyncEx库](http://nitoasyncex.codeplex.com/)中的方法。正式的.NET库没有为混合同步和异步代码提供太多帮助(通常,您不应该这样做!)。我正在等待.NET 4.5 RTW和一台新的非XP笔记本电脑,然后将AsyncEx更新为可在4.5上运行(我目前无法为4.5进行开发,因为我在XP上呆了几周)。 (2认同)
  • [此处](https://blog.stephencleary.com/2014/12/a-tour-of-task-part-6-results.html) @StephenCleary 解释说 GetAwaiter().GetResult() 是一个不错的选择。 (2认同)
  • @bluejayke:安装 [`Nito.AsyncEx`](https://github.com/StephenCleary/AsyncEx) 库。或者,使用“.GetAwaiter().GetResult()”而不是“.WaitAndUnwrapException()”。 (2认同)

Toh*_*hid 265

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

首先阅读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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案应该刻在石头上 (54认同)
  • @ChrisPratt - 你可能是对的,因为`Task.Run()`不是异步代码中的最佳实践.但是,再一次,原始问题的答案是什么?永远不要同步调用异步方法?我们希望,但在现实世界中,有时我们必须这样做. (23认同)
  • 有点疯狂,.NET 5.0 已经发布了,而且仍然没有万无一失的方法来同步调用异步方法。 (18认同)
  • 这不会死锁,这是真的,但仅仅是因为它被强制在原始线程的同步上下文之外的新线程中运行.但是,在某些环境中,这是非常不明智的:特别是Web应用程序.这可以有效地将Web服务器的可用线程减半(请求的一个线程和此一个线程).你做的越多,它就越糟糕.您可能最终导致整个Web服务器死锁. (17认同)
  • 两年过去了,我很想知道这个解决方案是如何坚持下去的.任何新闻?对新手来说,这种方法是否有微妙之处? (5认同)
  • @Tohid 你可以试试 Stephen Cleary 的图书馆。我已经看到人们认为这一点和“Parallel.ForEach”滥用不会对“现实世界”产生影响,最终它关闭了服务器。此代码适用于控制台应用程序,但正如@ChrisPratt 所说,不应在 Web 应用程序中使用。它可能“现在”工作,但不可扩展。 (2认同)
  • @JHBonarius 在第一种情况下,任务在后台线程上执行,结果由原始上下文通过 `Result` 属性获取。在第二种情况下,任务在相同的上下文中执行,如果该上下文是 UI 线程,则会导致死锁。 (2认同)

Eri*_*ips 189

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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));
}
Run Code Online (Sandbox Code Playgroud)

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

  • 我的异步方法等待其他异步方法.我不用`ConfigureAwait(false)`来装饰我的`await`调用.我尝试使用`AsyncHelper.RunSync`从Global.asax中的`Application_Start()`函数调用异步函数,它似乎工作.这是否意味着`AsyncHelper.RunSync`可靠地不容易"回调到调用者的上下文"死锁问题我在这篇文章的其他地方读过这个问题? (2认同)

Lee*_*ith 134

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

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

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


static async Task MainAsync()
{
   /*await stuff here*/
}
Run Code Online (Sandbox Code Playgroud)

您将在许多Microsoft文档中看到这种情况,例如:https: //docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-主题的订阅

  • 我不知道为什么有人投了这票.这对我很有用.如果没有这个修复,我将不得不在任何地方传播ASYCH. (10认同)
  • 为什么这比'MainAsync()更好.Wait()`? (9认同)
  • 我同意.你只需要MainAsync().Wait()而不是所有这些. (7认同)
  • @crush我在描述这是如何避免一些死锁的.在某些情况下,从UI或asp.net线程调用.Wait()会导致死锁.[async deadlocks](http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html) (7认同)
  • @ClintB:你绝对不应该在ASP.NET Core中这样做.Web应用程序特别容易受到线程缺乏的攻击,每次执行此操作时,您都会从池中提取一个线程,否则该线程将用于提供请求.对桌面/移动应用程序来说问题较少,因为它们传统上是单用户. (5认同)
  • @David解释它如何帮助避免死锁. (3认同)
  • 这对我有用......在Xamarin Android应用程序中! (3认同)
  • 这个伟大的答案与 Stephen Cleary 接受的答案相同,更新为 2021 年,如他自己的评论中所示,但没有所有复杂的“解释”。是的,在例如网络应用程序中,这可能不是一个好的实践,异步的东西不是无缘无故发明的,但如果你需要在同步环境中调用异步方法,就是这样。 (3认同)
  • Wait() 将异常收集到 AggregateException 中,GetAwaiter().GetResult() 返回抛出的异常。 (2认同)
  • 如果您不从 UI 线程调用,则可以采用这种方法,因为它不会不必要地使用更多线程。 (2认同)

Des*_*tar 49

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

}
Run Code Online (Sandbox Code Playgroud)

您将'await'关键字读作"启动此长时间运行的任务,然后将控制权返回给调用方法".一旦长时间运行的任务完成,它就会执行后面的代码.await之后的代码类似于以前的CallBack方法.逻辑流程的最大区别不在于中断,这使得编写和读取更加容易.

  • `Wait`包含异常并有可能出现死锁. (10认同)
  • @TrueBlueAussie这是同步上下文死锁.你的异步代码会回调到同步上下文,但当时被`Result`调用阻塞,所以它永远不会到达那里.而且"结果"永远不会结束,因为它正在等待等待"结果"结束的人,基本上是:D (7认同)
  • 我喜欢这个答案.编辑的好评,小巧,优雅.谢谢你的贡献!我还在学习并发,所以一切都有帮助:) (2认同)
  • 这个答案在今天仍然有效吗?我刚刚在一个MVC Razor项目中试过它,应用程序只是挂起来访问`.Result`. (2认同)

NSt*_*uke 36

我不是百分百肯定,但我相信这个博客中描述的技术应该适用于很多情况:

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

  • 上面的Stephen Cleary的[答案](http://stackoverflow.com/a/9343733/450913)中的解决方案A使用了这种方法.请参阅[WaitAndUnwrapException](https://github.com/StephenCleary/AsyncEx/blob/edb2c6b66d41471008a56e4098f9670b5143617e/src/Nito.AsyncEx.Tasks/SynchronousTaskExtensions.cs#L17-L22)源. (5认同)
  • 这就是“许多情况”的部分。它取决于整体线程模型以及其他线程正在执行的操作来确定是否存在死锁风险。 (2认同)

Rob*_*t J 23

最常被接受的答案并不完全正确.有一种适用于所有情况的解决方案:ad-hoc消息泵(SynchronizationContext).

调用线程将按预期被阻塞,同时仍然确保从异步函数调用的所有continuation都不会死锁,因为它们将被封送到在调用线程上运行的ad-hoc 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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

AsyncPump.Run(() => FooAsync(...));
Run Code Online (Sandbox Code Playgroud)

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

  • 这在 Asp.net 场景中不起作用,因为您可能会随机丢失 HttpContext.Current。 (3认同)

pan*_*wel 13

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

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

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

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


Hub*_*kul 11

斯蒂芬·克利里的回答;

\n
\n

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

\n
\n

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

\n

这是方法;

\n
\n

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

\n

图 7 线程池黑客代码

\n

C#

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

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

\n
\n


jry*_*uer 9

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

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

在内部,它调用起来task.GetAwaiter().GetResult()很简单,但是在任何async可以返回的方法上工作TaskTask<T>或者Task<HttpResponseMessage>...语法糖,宝贝...爸爸都很喜欢吃甜食。

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

  • 你让我“就像完全只是街区”。 (4认同)
  • task.GetAwaiter().GetResult() 对我来说总是导致死锁。 (2认同)

bas*_*se2 8

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

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

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

  • 那么现在就不会同步调用该方法了吧? (7认同)
  • 据我了解,问题是您能否从非异步方法调用异步方法。这并不意味着必须以阻塞方式调用异步方法。 (2认同)
  • 抱歉,您的“它们也必须标记为‘异步’”使我的注意力从您真正所说的内容上转移开。 (2认同)
  • 如果我真的不关心异步性,这样调用它可以吗(Stephen Cleary 一直在抱怨的包装异常中死锁的可能性怎么样?)我有一些测试方法(必须同步执行)测试异步方法。我必须等待结果才能继续,这样我就可以测试异步方法的结果。 (2认同)

小智 6

var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)

或使用此:

var result=result.GetAwaiter().GetResult().AccessToken
Run Code Online (Sandbox Code Playgroud)


Wah*_*tar 5

我知道我来晚了。但是,如果像我这样的人想要以一种整洁,简单的方式解决此问题,并且无需依赖其他库。

我从Ryan找到了以下代码

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}
Run Code Online (Sandbox Code Playgroud)

那么你可以这样称呼它

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());
Run Code Online (Sandbox Code Playgroud)

  • 这看起来与上面的[answer](/sf/answers/1756824891/)完全一样 (4认同)

Met*_*gic 5

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

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();
}
Run Code Online (Sandbox Code Playgroud)

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

RunSync(() => Foo());
var result = RunSync(() => FooWithResult());
Run Code Online (Sandbox Code Playgroud)

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


归档时间:

查看次数:

540564 次

最近记录:

6 年 前