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

发布于 2025-01-07 15:12:36 字数 446 浏览 6 评论 0原文

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

这可能吗?

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

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

I have a public async Task Foo() method that I want to call from a synchronous method. So far all I have seen from MSDN documentation is calling async methods via async methods, but my whole program is not built with async methods.

Is this even possible?

Here's one example of calling these methods from an asynchronous method:
Walkthrough: Accessing the Web by Using Async and Await (C# and Visual Basic)

Now I'm looking into calling these async methods from synchronous methods.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

吻风 2025-01-14 15:12:36

异步编程确实通过代码库“增长”。它已经与僵尸病毒相比。最好的解决办法是让它继续生长,但有时这是不可能的。

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

解决方案 A

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

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

不< /em> 希望使用 Task.WaitTask.Result 因为它们将异常包装在 AggregateException 中。

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

解决方案 B

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

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

*更新 4/14/2014:在该库的最新版本中,API 如下:(

var result = AsyncContext.Run(MyAsyncMethod);

在此示例中使用 Task.Result 是可以的,因为 RunTask 将传播任务例外)。

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

  1. :方法调用异步方法,获取Task
  2. 同步方法对 Task 执行阻塞等待。
  3. async 方法使用 await,而不使用 ConfigureAwait
  4. 在这种情况下,Task 无法完成,因为它仅在 async 方法完成时完成; 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。

更新: 2015 MSDN 文章'异步编程 - Brownfield 异步开发',作者:Stephen Cleary。

Asynchronous programming does "grow" through the code base. It has been compared to a zombie virus. The best solution is to allow it to grow, but sometimes that's not possible.

I have written a few types in my Nito.AsyncEx library for dealing with a partially-asynchronous code base. There's no solution that works in every situation, though.

Solution A

If you have a simple asynchronous method that doesn't need to synchronize back to its context, then you can use Task.WaitAndUnwrapException:

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

You do not want to use Task.Wait or Task.Result because they wrap exceptions in AggregateException.

This solution is only appropriate if MyAsyncMethod does not synchronize back to its context. In other words, every await in MyAsyncMethod should end with ConfigureAwait(false). This means it can't update any UI elements or access the ASP.NET request context.

Solution B

If MyAsyncMethod does need to synchronize back to its context, then you may be able to use AsyncContext.RunTask to provide a nested context:

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

*Update 4/14/2014: In more recent versions of the library the API is as follows:

var result = AsyncContext.Run(MyAsyncMethod);

(It's OK to use Task.Result in this example because RunTask will propagate Task exceptions).

The reason you may need AsyncContext.RunTask instead of Task.WaitAndUnwrapException is because of a rather subtle deadlock possibility that happens on WinForms/WPF/SL/ASP.NET:

  1. A synchronous method calls an async method, obtaining a Task.
  2. The synchronous method does a blocking wait on the Task.
  3. The async method uses await without ConfigureAwait.
  4. The Task cannot complete in this situation because it only completes when the async method is finished; the async method cannot complete because it is attempting to schedule its continuation to the SynchronizationContext, and WinForms/WPF/SL/ASP.NET will not allow the continuation to run because the synchronous method is already running in that context.

This is one reason why it's a good idea to use ConfigureAwait(false) within every async method as much as possible.

Solution C

AsyncContext.RunTask won't work in every scenario. For example, if the async method awaits something that requires a UI event to complete, then you'll deadlock even with the nested context. In that case, you could start the async method on the thread pool:

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

However, this solution requires a MyAsyncMethod that will work in the thread pool context. So it can't update UI elements or access the ASP.NET request context. And in that case, you may as well add ConfigureAwait(false) to its await statements, and use solution A.

Update: 2015 MSDN article 'Async Programming - Brownfield Async Development' by Stephen Cleary.

神妖 2025-01-14 15:12:36

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

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

Adding a solution that finally solved my problem, hopefully saves somebody's time.

Firstly read a couple articles of Stephen Cleary:

From the "two best practices" in "Don't Block on Async Code", the first one didn't work for me and the second one wasn't applicable (basically if I can use await, I do!).

So here is my workaround: wrap the call inside a Task.Run<>(async () => await FunctionAsync()); and hopefully no deadlock anymore.

Here is my code:

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;
    }
}
倾城月光淡如水﹏ 2025-01-14 15:12:36

Microsoft 构建了一个 AsyncHelper(内部)类来将异步作为同步运行。源代码如下所示:

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 基类仅具有异步方法,为了将它们称为同步,有一些带有扩展方法的类,如下所示(示例用法):

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()).Result 一样吗? AFAIK,微软现在不鼓励调用 TaskFactory.StartNew,因为它们都是等效的,并且一个比另一个更具可读性。

绝对不是。

简单的答案是,

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

首先

Task.Result 是否与 . GetAwaiter.GetResult()?

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

这应该会导致任何人问

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

然后这将是一个It Depends

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

摘录:

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

Task.Factory.StartNew 使用 TaskScheduler.Current 表示当前线程的调度程序,它可能是 TaskScheduler.Default 但不总是

附加阅读:

指定同步上下文

ASP.NET Core SynchronizationContext

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

alex-from-jitbit<的观点非常好/a> 和大多数对象架构问题一样,这取决于

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

Microsoft built an AsyncHelper (internal) class to run Async as Sync. The source looks like:

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

The Microsoft.AspNet.Identity base classes only have Async methods and in order to call them as Sync there are classes with extension methods that look like (example usage):

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

For those concerned about the licensing terms of code, here is a link to very similar code (just adds support for culture on the thread) that has comments to indicate that it is MIT Licensed by Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

Wouldn't this be the same as just calling Task.Run(async ()=> await AsyncFunc()).Result? AFAIK, Microsoft is now discouraging from calling TaskFactory.StartNew, since they are both equivalent and one is more readable than the other.

Absolutely not.

The easy answer is that

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

First off the

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

Secondly .Unwrap() causes the setup of the Task not to block the wrapped task.

Which should lead anyone to ask

Wouldn't this be the same as just calling Task.Run(async ()=> await AsyncFunc()).GetAwaiter().GetResult()

Which would then be a It Depends.

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

Excerpt:

Task.Run uses TaskCreationOptions.DenyChildAttach which means that children's tasks can not be attached to the parent and it uses TaskScheduler.Default which means that the one that runs tasks on Thread Pool will always be used to run tasks.

Task.Factory.StartNew uses TaskScheduler.Current which means scheduler of the current thread, it might be TaskScheduler.Default but not always.

Additional Reading:

Specifying a synchronization context

ASP.NET Core SynchronizationContext

For extra safety, wouldn't it be better to call it like this AsyncHelper.RunSync(async () => await AsyncMethod().ConfigureAwait(false));This way we're telling the "inner" method "please don't try to sync to upper context and dealock"

Really great point by alex-from-jitbit and as most object architectural questions go it depends.

As an extension method do you want to force that for absolutely every call, or do you let the programmer using the function configure that on their own async calls? I could see a use case for call three scenarios; it most likely is not something you want in WPF, certainly makes sense in most cases, but considering there is no Context in ASP.Net Core if you could guarantee it was say internal for a ASP.Net Core, then it wouldn't matter.

咋地 2025-01-14 15:12:36

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

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

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

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

你会在很多微软文档中看到这个用法,例如:
获取从 Azure 服务总线主题和订阅开始

async Main() is now part of C# 7.2 and can be enabled in the projects advanced build settings.

For C# < 7.2, the correct way is:

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

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

You'll see this used in a lot of Microsoft documentation, for example:
Get started with Azure Service Bus topics and subscriptions.

负佳期 2025-01-14 15:12:36

我不是 100% 确定,但我相信 此博客应该适用于多种情况:

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

I'm not 100% sure, but I believe the technique described in this blog should work in many circumstances:

You can thus use task.GetAwaiter().GetResult() if you want to directly invoke this propagation logic.

[旋木] 2025-01-14 15:12:36
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”关键字理解为“启动此长时间运行的任务,然后将控制权返回给调用方法”。一旦长时间运行的任务完成,它就会执行其后的代码。 wait 之后的代码与以前的 CallBack 方法类似。最大的区别是逻辑流程不会被中断,这使得写入和读取变得更加容易。

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

}

You read the 'await' keyword as "start this long running task, then return control to the calling method". Once the long-running task is done, then it executes the code after it. The code after the await is similar to what used to be CallBack methods. The big difference being the logical flow is not interrupted which makes it much easier to write and read.

岁月静好 2025-01-14 15:12:36

然而,有一个适用于(几乎:参见评论)每种情况的良好解决方案:即席消息泵(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(...));

异步泵的更详细说明可参见 此处

There is, however, a good solution that works in (almost: see comments) every situation: an ad-hoc message pump (SynchronizationContext).

The calling thread will be blocked as expected, while still ensuring that all continuations called from the async function don't deadlock as they'll be marshaled to the ad-hoc SynchronizationContext (message pump) running on the calling thread.

The code of the ad-hoc message pump helper:

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

Usage:

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

More detailed description of the async pump is available here.

樱桃奶球 2025-01-14 15:12:36

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

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

它在内部调用 task.GetAwaiter().GetResult() ,这非常简单,但是它会重载以处理任何返回 Taskasync 方法。 、 TaskTask...语法糖,宝贝...爸爸爱吃甜食。

看起来 ...GetAwaiter().GetResult() 是在阻塞上下文中执行异步代码的 MS 官方方法。似乎非常适合我的用例。

To anyone paying attention to this question anymore...

If you look in Microsoft.VisualStudio.Services.WebApi there's a class called TaskExtensions. Within that class you'll see the static extension method Task.SyncResult(), which like totally just blocks the thread till the task returns.

Internally it calls task.GetAwaiter().GetResult() which is pretty simple, however it's overloaded to work on any async method that return Task, Task<T> or Task<HttpResponseMessage>... syntactic sugar, baby... daddy's got a sweet tooth.

It looks like ...GetAwaiter().GetResult() is the MS-official way to execute async code in a blocking context. Seems to work very fine for my use case.

感性 2025-01-14 15:12:36
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

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

或者使用这个:

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

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

Or use this:

var result=result.GetAwaiter().GetResult().AccessToken
后eg是否自 2025-01-14 15:12:36

您可以从同步代码调用任何异步方法,也就是说,直到您需要 await 时,它们也必须标记为 async

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

如果您确实无法使您的方法异步并且您不想锁定同步方法,那么您将不得不使用回调方法,将其作为参数传递给<任务的 code>ContinueWith() 方法。

You can call any asynchronous method from synchronous code, that is, until you need to await on them, in which case they have to be marked as async too.

As a lot of people are suggesting here, you could call Wait() or Result on the resulting task in your synchronous method, but then you end up with a blocking call in that method, which sort of defeats the purpose of async.

If you really can't make your method async and you don't want to lock up the synchronous method, then you're going to have to use a callback method by passing it as parameter to the ContinueWith() method on task.

橘味果▽酱 2025-01-14 15:12:36

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

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

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

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

Here is the simplest solution. I saw it somewhere on the Internet, I didn't remember where, but I have been using it successfully. It will not deadlock the calling thread.

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

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

    string SynchronousFunctionReturnsStringWithParam(int id)
    {
        return Task.Run(() => Foo(id)).Result;
    }
段念尘 2025-01-14 15:12:36

斯蒂芬·克利里的回答;

这种方法不应该导致死锁(假设
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 元素或 ASP.NET HttpContext.Current。

Stephen Cleary's Answer;

That approach shouldn't cause a deadlock (assuming that
ProblemMethodAsync doesn't send updates to the UI thread or anything
like that). It does assume that ProblemMethodAsync can be called on a
thread pool thread, which is not always the case.

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

And here is the approach;

The Thread Pool Hack A similar approach to the Blocking Hack is to
offload the asynchronous work to the thread pool, then block on the
resulting task. The code using this hack would look like the code
shown in Figure 7.

Figure 7 Code for the Thread Pool 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);
  }
}

The call to Task.Run executes the asynchronous method on a thread pool
thread. Here it will run without a context, thus avoiding the
deadlock. One of the problems with this approach is the asynchronous
method can’t depend on executing within a specific context. So, it
can’t use UI elements or the ASP.NET HttpContext.Current.

哽咽笑 2025-01-14 15:12:36

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

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

Inspired by some of the other answers, I created the following simple helper methods:

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

They can be called as follows (depending on whether you are returning a value or not):

RunSync(() => Foo());
var result = RunSync(() => FooWithResult());
浊酒尽余欢 2025-01-14 15:12:36

我多年来一直使用这种方法,它还处理和传播底层异步任务的异常。效果完美。

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

    return task.Result;
}

但自从微软创建了这个异步助手: 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();
        }

Well I was using this approach for years, which also handles and propagates exceptions from the underlying async task. Which works flawlessly.

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

    return task.Result;
}

But since that Microsoft created this async helper: https://github.com/aspnet/AspNetIdentity/blob/main/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

Here is also their source:

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();
        }
独守阴晴ぅ圆缺 2025-01-14 15:12:36

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

按如下方式使用它:

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

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

You can now use source generators to create a sync version of your method using Sync Method Generator library (nuget).

Use it as follows:

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

Which will generate Foo method which you can call synchronously.

汐鸠 2025-01-14 15:12:36

每个人似乎都预设需要等待结果。
我经常需要从同步方法更新数据,而我并不关心结果。我只是使用丢弃:

_ = UpdateAsync();

Everyone seems to presuppose that there is a need to wait for the result.
I often have to update data from synchronous methods where I don't care about the result. I just use a discard:

_ = UpdateAsync();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文