How to call asynchronous method from synchronous method in C#?
我有一个
这是可能的吗?
以下是从异步方法调用这些方法的一个示例:http://msdn.microsoft.com/en-us/library/hh300224(v=v s.110).aspx
现在我正在研究从同步方法调用这些异步方法。
异步编程通过代码库"增长"。它被比作一种僵尸病毒。最好的解决方案是允许它增长,但有时这是不可能的。
我在nito.asyncEx库中编写了一些类型,用于处理部分异步的代码基。但在任何情况下都没有解决方案。
解决方案A
如果您有一个简单的异步方法不需要同步回其上下文,那么您可以使用
1 2 | var task = MyAsyncMethod(); var result = task.WaitAndUnwrapException(); |
您不想使用
只有当
解决方案B
如果
1 | var result = AsyncContext.RunTask(MyAsyncMethod).Result; |
*2014年4月14日更新:在库的最新版本中,API如下:
1 | var result = AsyncContext.Run(MyAsyncMethod); |
(本例中可以使用
您可能需要
这就是为什么在每个
C解决方案
1 2 | var task = TaskEx.RunEx(async () => await MyAsyncMethod()); var result = task.WaitAndUnwrapException(); |
但是,这个解决方案需要一个在线程池上下文中工作的
更新,2019-05-01:当前的"最差实践"在这里的一篇msdn文章中。
添加一个解决方案,最终解决了我的问题,希望能节省别人的时间。
首先阅读斯蒂芬·克利里的几篇文章:
- 异步等待
- 不要阻止异步代码
从"不要阻塞异步代码"中的"两个最佳实践"来看,第一个不适用于我,第二个不适用(基本上如果我可以使用
所以这里是我的解决方法:将调用包装在一个
这是我的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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; } } |
Microsoft构建了一个AsyncHelper(内部)类,以同步方式运行Async。资料来源如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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基类只有异步方法,为了将它们作为同步调用,有一些类的扩展方法如下(示例用法):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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)); } |
对于那些关心代码许可条款的人,这里有一个链接指向非常相似的代码(只是在线程上添加了对文化的支持),该代码有注释来表明它是由微软授权的MIT。https://github.com/aspnet/aspnetidity/blob/master/src/microsoft.aspnet.identity.core/asyncHelper.cs
AsyncMain现在是C 7.2的一部分,可以在项目高级生成设置中启用。
对于c<7.2,正确的方法是:
1 2 3 4 5 6 7 8 9 10 | static void Main(string[] args) { MainAsync().GetAwaiter().GetResult(); } static async Task MainAsync() { /*await stuff here*/ } |
您将在许多Microsoft文档中看到这一点,例如:https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-topics-subscriptions-订阅
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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之后的代码类似于以前的回调方法。最大的区别是逻辑流没有中断,这使得写和读更容易。
我不是百分之百确定,但我相信这个博客中描述的技术在很多情况下都能发挥作用:
You can thus use
task.GetAwaiter().GetResult() if you want to directly invoke this propagation logic.
最普遍接受的答案并不完全正确。在每种情况下都有一种解决方案:即席消息泵(SynchronizationContext)。
调用线程将按预期被阻塞,同时仍然确保从异步函数调用的所有延续不会死锁,因为它们将被封送到调用线程上运行的临时同步上下文(消息泵)。
特设消息泵帮助程序的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | 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(); } } } } |
用途:
1 | AsyncPump.Run(() => FooAsync(...)); |
这里提供了异步泵的更详细的描述。
任何关注这个问题的人…
如果你看
在内部它称为
看起来
您可以从同步代码调用任何异步方法,也就是说,在需要对它们执行
正如很多人在这里建议的那样,您可以在同步方法中调用wait()或对结果任务执行result,但最终会在该方法中调用一个阻塞调用,这有点破坏了异步的目的。
如果你真的不能使你的方法
我知道我迟到了。但如果像我这样的人想用一种整洁、简单的方式解决这个问题,而不依赖于另一个库。
我找到了Ryan的以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public static class AsyncHelpers { private static readonly TaskFactory taskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default); /// <summary> /// Executes an async Task method which has a void return value synchronously /// USAGE: AsyncUtil.RunSync(() => AsyncMethod()); /// </summary> /// <param name="task">Task method to execute</param> public static void RunSync(Func<Task> task) => taskFactory .StartNew(task) .Unwrap() .GetAwaiter() .GetResult(); /// <summary> /// Executes an async Task<T> method which has a T return type synchronously /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>()); /// </summary> /// <typeparam name="TResult">Return Type</typeparam> /// <param name="task">Task<T> method to execute</param> /// <returns></returns> public static TResult RunSync<TResult>(Func<Task<TResult>> task) => taskFactory .StartNew(task) .Unwrap() .GetAwaiter() .GetResult(); } |
然后你可以这样称呼它
1 | var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>()); |
1 2 3 | var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false); OpenIdConnectConfiguration config = result.GetAwaiter().GetResult(); |
或使用此:
1 | var result=result.GetAwaiter().GetResult().AccessToken |
如果要运行它,请同步
1 | MethodAsync().RunSynchronously() |
这些Windows异步方法有一个漂亮的小方法,叫做astask()。您可以使用此方法将该方法本身作为任务返回,以便可以手动对其调用wait()。
例如,在Windows Phone 8 Silverlight应用程序上,可以执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 // ..... } |
希望这有帮助!
1 2 3 4 5 6 7 8 9 10 11 12 | //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; |