我想从同步方法调用public async void Foo()
方法。到目前为止,我从MSDN文档中看到的是通过异步方法调用异步方法,但我的整个程序不是用异步方法构建的。
这甚至可能吗?
以下是从异步方法调用这些方法的一个示例:http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx
现在我正在研究从同步方法中调用这些异步方法。
答案 0 :(得分:550)
异步编程通过代码库“增长”。它一直是compared to a zombie virus。最好的解决方案是让它成长,但有时这是不可能的。
我在Nito.AsyncEx库中编写了一些类型来处理部分异步代码库。但是,没有适用于所有情况的解决方案。
解决方案A
如果您有一个不需要同步回其上下文的简单异步方法,那么您可以使用Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
您不想要使用Task.Wait
或Task.Result
,因为它们会在AggregateException
中包含例外。
此解决方案仅在MyAsyncMethod
未同步回其上下文时才适用。换句话说,await
中的每个MyAsyncMethod
都应以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将不允许继续运行,因为同步方法已在运行那个背景。这就是为什么尽可能在每个ConfigureAwait(false)
方法中使用async
的好主意之一。
解决方案C
AsyncContext.RunTask
在每种情况下都不起作用。例如,如果async
方法等待需要UI事件完成的内容,那么即使使用嵌套上下文也会出现死锁。在这种情况下,您可以在线程池上启动async
方法:
var task = TaskEx.RunEx(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
但是,此解决方案需要MyAsyncMethod
,它将在线程池上下文中起作用。因此它无法更新UI元素或访问ASP.NET请求上下文。在这种情况下,您也可以将ConfigureAwait(false)
添加到其await
语句中,并使用解决方案A.
更新,2019-05-01:当前“最差最差做法”是MSDN article here。
答案 1 :(得分:212)
添加一个最终解决了我的问题的解决方案,希望能节省一些时间。
首先阅读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;
}
}
答案 2 :(得分:174)
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));
}
对于那些关注代码许可条款的人来说,这里有一个链接到非常相似的代码(只是在线程上增加了对文化的支持),这些代码有注释表明它是由Microsoft授权的MIT。 https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
答案 3 :(得分:108)
async Main现在是C#7.2的一部分,可以在项目高级构建设置中启用。
对于C#&lt; 7.2,正确的方法是:
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
答案 4 :(得分:46)
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方法。逻辑流程的最大区别不在于中断,这使得写入和读取更加容易。
答案 5 :(得分:31)
我不是百分百肯定,但我相信this blog中描述的技术在许多情况下都适用:
如果要直接调用此传播逻辑,可以使用
task.GetAwaiter().GetResult()
。
答案 6 :(得分:5)
对于任何再关注这个问题的人...
如果您查看Microsoft.VisualStudio.Services.WebApi
,则有一个名为TaskExtensions
的类。在该类中,您将看到静态扩展方法Task.SyncResult()
,就像完全阻塞线程,直到任务返回一样。
内部调用task.GetAwaiter().GetResult()
很简单,但是它可以重载返回async
,Task
或Task<T>
的任何Task<HttpResponseMessage>
方法。语法糖,宝贝...爸爸的牙齿很甜。
看来...GetAwaiter().GetResult()
是在阻塞上下文中执行异步代码的MS官方方法。对于我的用例来说似乎工作得很好。
答案 7 :(得分:3)
您可以使用同步代码调用任何异步方法,也就是说,直到需要await
为止,在这种情况下,它们也必须标记为async
。
正如很多人在这里建议的那样,你可以在同步方法中调用结果任务上的Wait()或Result,但最后你会在该方法中调用阻塞调用,这会破坏异步的目的。
我真的无法使你的方法async
并且你不想锁定同步方法,那么你将不得不通过将它作为参数传递给ContinueWith来使用回调方法任务方法。
答案 8 :(得分:2)
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);
OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();
或使用此:
var result=result.GetAwaiter().GetResult().AccessToken
答案 9 :(得分:2)
我知道我来晚了。 但是,如果像我这样的人想要以一种整洁,简单的方式来解决这个问题,而又不必依赖其他库。
我从piece of code找到了以下Ryan
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>());
答案 10 :(得分:2)
经过数小时尝试不同方法的尝试,或多或少都取得了成功,这就是我的结局。它不会在获取结果时陷入死锁,它还会获取并抛出原始异常,而不是包装的异常。
private ReturnType RunSync()
{
var task = Task.Run(async () => await myMethodAsync(agency));
if (task.IsFaulted && task.Exception != null)
{
throw task.Exception;
}
return task.Result;
}
答案 11 :(得分:1)
受其他答案的启发,我创建了以下简单的辅助方法:
public static TResult RunSync<TResult>(Func<Task<TResult>> method)
{
var task = method();
return task.GetAwaiter().GetResult();
}
public static void RunSync(Func<Task> method)
{
var task = method();
task.GetAwaiter().GetResult();
}
可以按如下方式调用它们(取决于您是否返回值):
RunSync(() => Foo());
var result = RunSync(() => FooWithResult());
请注意,原始问题public async void Foo()
中的签名不正确。应该为public async Task Foo()
,因为对于不返回值的异步方法,您应该返回Task而不为空(是的,有一些罕见的例外)。
答案 12 :(得分:-1)
如果要运行“同步”
MethodAsync().RunSynchronously()
答案 13 :(得分:-1)
这是最简单的解决方案。我在互联网上的某个地方看到过它,我不记得在哪里,但是我一直在成功使用它。它不会死锁调用线程。
void Synchronous Function()
{
Task.Run(Foo).Wait();
}
string SynchronousFunctionReturnsString()
{
return Task.Run(Foo).Result;
}
string SynchronousFunctionReturnsStringWithParam(int id)
{
return Task.Run(() => Foo(id)).Result;
}
答案 14 :(得分:-3)
那些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
// .....
}
希望这有帮助!
答案 15 :(得分:-4)
//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;