赞
踩
我有一个public async void Foo()
方法,我想从同步方法中调用它。 到目前为止,我从MSDN文档中看到的所有内容都是通过异步方法调用异步方法,但是我的整个程序不是使用异步方法构建的。
这有可能吗?
这是从异步方法调用这些方法的一个示例: http : //msdn.microsoft.com/zh-cn/library/hh300224(v=vs.110).aspx
现在,我正在研究从同步方法调用这些异步方法。
我不确定100%,但是我相信此博客中描述的技术在许多情况下都可以使用:
因此,如果要直接调用此传播逻辑,则可以使用
task.GetAwaiter().GetResult()
。
- //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;
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();
- }
- }
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));
- }
对于那些关心代码许可条款的人来说,这里是指向非常相似的代码的链接(只是增加了对线程的区域性的支持),并带有注释以表明它已获得MIT的Microsoft许可。 https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
异步Main现在是C#7.2的一部分,可以在项目高级构建设置中启用。
对于C#<7.2,正确的方法是:
- static void Main(string[] args)
- {
- MainAsync().GetAwaiter().GetResult();
- }
-
-
- static async Task MainAsync()
- {
- /*await stuff here*/
- }
您会在许多Microsoft文档中看到它,例如: https : //docs.microsoft.com/zh-cn/azure/service-bus-messaging/service-bus-dotnet-how-to-use-主题-订阅
这些Windows异步方法有一个漂亮的小方法,称为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
- // .....
- }
希望这可以帮助!
最被接受的答案并不完全正确。 有一种适用于各种情况的解决方案:即席消息泵(SynchronizationContext)。
调用线程将按预期方式被阻止,同时仍确保从异步函数调用的所有连续都不会死锁,因为它们将被封送到调用线程上运行的临时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(...));
有关异步泵的更多详细说明,请参见此处 。
添加最终解决了我的问题的解决方案,希望可以节省一些时间。
首先阅读Stephen Cleary的几篇文章:
根据“不要阻止异步代码”中的“两个最佳实践”,第一个不适用于我,第二个不适用于(基本上,如果我可以使用await
,我可以!)。
所以这是我的解决方法:将调用包装在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;
- }
- }
- var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);
-
- OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();
或使用此:
var result=result.GetAwaiter().GetResult().AccessToken
对于任何关注这个问题的人来说...
如果您查看Microsoft.VisualStudio.Services.WebApi
则有一个名为TaskExtensions
的类。 在该类中,您将看到静态扩展方法Task.SyncResult()
,它就像完全阻塞线程一样,直到任务返回。
它在内部调用非常简单的task.GetAwaiter().GetResult()
,但是它可以重载以处理返回Task
, Task<T>
或Task<HttpResponseMessage>
...语法糖,宝贝...爸爸的任何async
方法爱吃甜食。
看起来...GetAwaiter().GetResult()
是在阻塞上下文中执行异步代码的MS官方方法。 对于我的用例来说似乎工作得很好。
我知道我来晚了。 但是,如果像我这样的人想要以一种整洁,简单的方式解决此问题,而又不必依赖其他库。
- 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();
- }
那么你可以这样称呼它
var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());
如果要运行它同步
MethodAsync().RunSynchronously()
您可以从同步代码中调用任何异步方法,也就是说,直到需要await
它们时,在这种情况下,它们也必须标记为async
。
正如很多人在这里建议的那样,您可以在同步方法中对结果任务调用Wait()或Result,但是最后在该方法中导致阻塞调用,这使异步的目的无效。
我真的不能使您的方法async
并且您不想锁定同步方法,那么您将不得不通过将回调方法作为参数传递给任务上的ContinueWith方法来使用回调方法。
- 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”读为“启动此长期运行的任务,然后将控制权返回给调用方法”。 长时间运行的任务完成后,它将在其后执行代码。 等待之后的代码类似于以前的CallBack方法。 最大的区别在于逻辑流程不会被打断,这使得写入和读取变得更加容易。
异步编程确实在代码库中“增长”。 它已经被比作僵尸病毒 。 最好的解决方案是允许它增长,但是有时这是不可能的。
我在Nito.AsyncEx库中编写了一些类型,用于处理部分异步的代码库。 但是,没有一种解决方案可以在每种情况下都适用。
解决方案A
如果您有一个简单的异步方法不需要同步回到其上下文,则可以使用Task.WaitAndUnwrapException
:
- var task = MyAsyncMethod();
- var result = task.WaitAndUnwrapException();
你不希望使用Task.Wait
或Task.Result
因为包装在异常AggregateException
。
仅当MyAsyncMethod
不同步回到其上下文时,此解决方案才适用。 换句话说, MyAsyncMethod
每个await
都应以ConfigureAwait(false)
结尾。 这意味着它无法更新任何UI元素或访问ASP.NET请求上下文。
解决方案B
如果MyAsyncMethod
确实需要同步回其上下文,则可以使用AsyncContext.RunTask
提供嵌套的上下文:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
* 2014年4月14日更新:在该库的最新版本中,API如下:
var result = AsyncContext.Run(MyAsyncMethod);
(这是确定使用Task.Result
在这个例子中,因为RunTask
将传播Task
除外)。
您可能需要AsyncContext.RunTask
而不是Task.WaitAndUnwrapException
的原因是由于WinForms / WPF / SL / ASP.NET上发生相当微妙的死锁:
Task
。 Task
进行阻塞等待。 async
方法使用await
而不使用ConfigureAwait
。 Task
无法完成,因为它仅在async
方法完成时才完成; async
方法无法完成,因为它正在尝试安排其继续到SynchronizationContext
,并且WinForms / WPF / SL / ASP.NET将不允许继续运行,因为同步方法已在该上下文中运行。 这就是为什么最好在每个async
方法中都使用ConfigureAwait(false)
原因之一。
解决方案C
AsyncContext.RunTask
并非在每种情况下都起作用。 例如,如果async
方法等待需要完成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文章在此处提供了当前的“最差实践”。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。