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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9343594/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 06:59:13  来源:igfitidea点击:

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

c#async-await

提问by Tower

I have a public async void Foo()method that I want to call from 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.

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

Is this even possible?

这甚至可能吗?

Here's one example of calling these methods from an asynchronous method: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

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

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

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

采纳答案by 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.AsyncExlibrary for dealing with a partially-asynchronous code base. There's no solution that works in every situation, though.

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

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:

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

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

You do notwant to use Task.Waitor Task.Resultbecause they wrap exceptions in AggregateException.

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

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

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

Solution B

方案B

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

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

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


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

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

var result = AsyncContext.Run(MyAsyncMethod);


(It's OK to use Task.Resultin this example because RunTaskwill propagate Taskexceptions).

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

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

您可能需要AsyncContext.RunTask而不是Task.WaitAndUnwrapException因为在 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 asyncmethod uses awaitwithout ConfigureAwait.
  4. The Taskcannot complete in this situation because it only completes when the asyncmethod is finished; the asyncmethod 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.
  1. 同步方法调用异步方法,获取Task.
  2. 同步方法在Task.
  3. async方法await不使用ConfigureAwait.
  4. Task这种情况无法完成,因为它只有在完成async方法完成; 该async方法无法完成,因为它正在尝试将其继续安排到SynchronizationContext,并且 WinForms/WPF/SL/ASP.NET 将不允许继续运行,因为同步方法已在该上下文中运行。

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

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

Solution C

方案C

AsyncContext.RunTaskwon't work in every scenario. For example, if the asyncmethod 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 asyncmethod on the thread pool:

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

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

However, this solution requires a MyAsyncMethodthat 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 awaitstatements, and use solution A.

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

Update, 2019-05-01:The current "least-worst practices" are in an MSDN article here.

更新,2019 年 5 月 1 日:当前的“最坏做法”在MSDN 文章中

回答by base2

You can call any asynchronous method from synchronous code, that is, until you need to awaiton them, in which case they have to be marked asynctoo.

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

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.

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

I you really can't make your method asyncand 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.

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

回答by 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

}

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.

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

回答by NStuke

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

我不是 100% 确定,但我相信本博客中描述的技术应该适用于许多情况:

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

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

回答by Arvind Kumar Chaodhary

   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;

回答by Erik Philips

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

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

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

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

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

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

回答by Lee Smith

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

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

For C# < 7.2, the correct way is:

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

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

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

回答by Foxy

Those windows async methods have a nifty little method called AsTask(). You can use this to have the method return itself as a task so that you can manually call Wait() on it.

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

For example, on a Windows Phone 8 Silverlight application, you can do the following:

例如,在 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 
    // .....
}

Hope this helps!

希望这可以帮助!

回答by Robert J

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

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

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

The code of the ad-hoc message pump helper:

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

Usage:

用法:

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

More detailed description of the async pump is available here.

此处提供了异步泵的更详细说明。

回答by Tohid

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

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

Firstly read a couple articles of Stephen Cleary:

首先阅读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!).

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

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

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

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