如何在C#中使用同步方法调用asynchronous方法?

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

这甚至有可能吗?

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

现在我正在调查从同步方法调用这些asynchronous方法。

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

我在我的Nito.AsyncEx库中编写了几个types来处理部分asynchronous的代码库。 尽pipe如此,在任何情况下都没有解决scheme。

答案A

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

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

您不想使用Task.WaitTask.Result因为它们在AggregateException包装exception。

此解决scheme只适用于MyAsyncMethod不同步回到其上下文。 换句话说, MyAsyncMethod每个await应以ConfigureAwait(false)结束。 这意味着它不能更新任何UI元素或访问ASP.NET请求上下文。

解决schemeB

如果MyAsyncMethod需要同步回到上下文,那么你可以使用AsyncContext.RunTask提供一个嵌套的上下文:

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

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

 var result = AsyncContext.Run(MyAsyncMethod); 

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

您可能需要AsyncContext.RunTask而不是Task.WaitAndUnwrapException的原因是因为在WinForms / WPF / SL / ASP.NET上发生了一个相当微小的死锁可能性:

  1. 同步方法调用一个asynchronous方法,获得一个Task
  2. 同步方法阻止等待Task
  3. async方法await没有ConfigureAwait情况下使用await
  4. 在这种情况下Task不能完成,因为只有在async方法完成时才能完成; async方法无法完成,因为它试图将其继续安排到SynchronizationContext ,并且WinForms / WPF / SL / ASP.NET将不允许继续运行,因为同步方法已经在该上下文中运行。

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

解决schemeC

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

 var task = TaskEx.RunEx(async () => await MyAsyncMethod()); var result = task.WaitAndUnwrapException(); 

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

微软build立了一个AsyncHelper(内部)类来运行asynchronous同步。 来源如下所示:

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

添加一个解决scheme,最终解决了我的问题,希望节省一些人的时间。

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

  • asynchronous和等待
  • 不要阻止asynchronous代码

从“不要封锁asynchronous代码”中的“两个最佳实践”中,第一个不适用于我,第二个不适用(基本上,如果我可以使用,我可以!)。

所以这里是我的解决方法:将调用封装在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; } } 
 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方法。 逻辑stream程的最大区别是不会中断,这使得写和读更容易。

 static void Main(string[] args) { Task.Run(async () => { await MainAsync();}).Wait(); } static async Task MainAsync() { /*await stuff here*/ } 

我不是100%肯定的,但我相信这个博客中描述的技术应该在很多情况下工作:

如果你想直接调用这个传播逻辑,你可以这样使用task.GetAwaiter().GetResult()

最被接受的答案并不完全正确。 在任何情况下都有一个解决scheme:一个专门的消息泵(SynchronizationContext)。

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

asynchronous泵的更详细的描述可以在这里find 。

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

正如很多人在这里所build议的,你可以在你的同步方法中调用Wait()或者Result对结果的任务进行调用,但是最终你会在该方法中阻塞调用,这种做法会影响asynchronous的目的。

我真的不能使你的方法async ,你不想locking同步的方法,那么你将不得不使用callback方法,通过将它作为parameter passing给任务上的ContinueWith方法。

那些windowsasynchronous方法有一个叫做AsTask()的漂亮小方法。 你可以使用这个方法让这个方法返回自己作为一个任务,这样你可以手动调用Wait()。

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

希望这可以帮助!

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

首先,你需要将public async void Foo()更改为public async Task Foo()async void只能在事件处理程序中使用。

例1:

 public async Task Foo(){...} var task = Foo(); task.RunSynchronously(); 

例2:

 public async Task<int> Bar(){...} var task = Bar(); task.RunSynchronously(); var result = task.Result; 

使用Task.Run <>方法来做到这一点。

这也是我如何执行所有LINQ查询和entity framework操作,没有-Async方法。

如果您使用的是MVVM, 请查看asynchronous的这些video并等待。