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

这可能吗?

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

现在我正在研究从sync方法调用这些async方法。


当前回答

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

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

异步和等待 不要阻塞异步代码

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

下面是我的解决方案:将调用包装在一个Task中。运行<>(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;
    }
}

其他回答

对于任何还在关注这个问题的人…

在微软。visualstudio。services。webapi中有一个叫TaskExtensions的类。在这个类中,你会看到静态扩展方法task . syncresult(),它会完全阻塞线程,直到任务返回。

在内部,它调用Task . getawaiter ().GetResult(),这是非常简单的,但是它重载工作在任何异步方法,返回Task, Task<T>或Task<HttpResponseMessage>…语法糖,宝贝…爸爸爱吃甜食。

getresult()是在阻塞上下文中执行异步代码的ms官方方式。似乎在我的用例中工作得很好。

然而,有一个很好的解决方案可以在(几乎:参见评论)任何情况下工作:一个特别的消息泵(SynchronizationContext)。

调用线程将按预期被阻塞,同时仍然确保从async函数调用的所有延续不会死锁,因为它们将被封送到运行在调用线程上的临时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(...));

异步泵的更详细的描述可在这里。

我不是100%确定,但我相信这篇博客中描述的技巧在许多情况下都适用:

因此,如果想直接调用这个传播逻辑,可以使用task.GetAwaiter(). getresult()。

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

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

解决方案一

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

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

您不想使用Task。等待或任务。结果,因为它们在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。因为RunTask将传播Task异常)。

你可能需要AsyncContext的原因。RunTask而不是Task。WaitAndUnwrapException是由于在WinForms/WPF/SL/ASP上可能会发生相当微妙的死锁。NET:

A synchronous method calls an async method, obtaining a Task. The synchronous method does a blocking wait on the Task. The async method uses await without ConfigureAwait. 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.

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

解决方案C

AsyncContext。RunTask并不适用于所有场景。例如,如果异步方法等待需要UI事件才能完成的事情,那么即使嵌套上下文也会死锁。在这种情况下,你可以在线程池上启动async方法:

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

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

更新日期:2019-05-01:当前的“最不坏的做法”见MSDN的一篇文章。

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

使用方法如下:

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

它会生成Foo方法,你可以同步调用。