如何在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.Wait
或Task.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
会传播Task
exception)。
您可能需要AsyncContext.RunTask
而不是Task.WaitAndUnwrapException
的原因是因为在WinForms / WPF / SL / ASP.NET上发生了一个相当微小的死锁可能性:
- 同步方法调用一个asynchronous方法,获得一个
Task
。 - 同步方法阻止等待
Task
。 -
async
方法await
没有ConfigureAwait
情况下使用await
。 - 在这种情况下
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并等待。
- 在方法名称中使用“asynchronous”后缀取决于是否使用“asynchronous”修饰符?
- C#中“return await”的目的是什么?
- 在.NET 4.0中创build一个可以与.NET 4.5中的“await”一起使用的asynchronous方法
- 在asynchronous方法结束时,我应该返回还是等待?
- 有人可以解释asynchronous/等待?
- 我必须做些什么来使我的方法等待?
- asynchronousTask.WhenAll超时
- 任何区别“await Task.Run(); 返回;“和”返回Task.Run()“?
- 同步等待asynchronous操作,为什么Wait()在这里冻结程序