Tow*_*wer 757 c# async-await
我有一个public async void Foo()
方法,我想从同步方法调用.到目前为止,我从MSDN文档中看到的是通过异步方法调用异步方法,但我的整个程序不是使用异步方法构建的.
这甚至可能吗?
以下是从异步方法调用这些方法的一个示例:http://msdn.microsoft.com/en-us/library/hh300224(v = vs.110).aspx
现在我正在研究从同步方法调用这些异步方法.
Ste*_*ary 635
异步编程通过代码库"增长".它已被比作僵尸病毒.最好的解决方案是让它成长,但有时这是不可能的.
我在Nito.AsyncEx库中编写了一些类型来处理部分异步代码库.但是,没有适用于所有情况的解决方案.
解决方案A.
如果您有一个简单的异步方法,不需要同步回其上下文,那么您可以使用Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Run Code Online (Sandbox Code Playgroud)
你不希望使用Task.Wait
或Task.Result
因为包装在异常AggregateException
.
只有在MyAsyncMethod
不同步回其上下文时,此解决方案才适用.换句话说,每一个await
在MyAsyncMethod
应该结束ConfigureAwait(false)
.这意味着它无法更新任何UI元素或访问ASP.NET请求上下文.
解决方案B.
如果MyAsyncMethod
确实需要同步回其上下文,那么您可以使用AsyncContext.RunTask
提供嵌套上下文:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
Run Code Online (Sandbox Code Playgroud)
*2014年4月14日更新:在更新版本的库中,API如下:
var result = AsyncContext.Run(MyAsyncMethod);
Run Code Online (Sandbox Code Playgroud)
(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 = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
Run Code Online (Sandbox Code Playgroud)
但是,此解决方案需要一个MyAsyncMethod
可在线程池上下文中工作的解决方案.因此它无法更新UI元素或访问ASP.NET请求上下文.在这种情况下,您也可以添加ConfigureAwait(false)
其await
语句,并使用解决方案A.
Toh*_*hid 265
添加一个最终解决了我的问题的解决方案,希望能节省一些人的时间.
首先阅读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;
}
}
Run Code Online (Sandbox Code Playgroud)
Eri*_*ips 189
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();
}
}
Run Code Online (Sandbox Code Playgroud)
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));
}
Run Code Online (Sandbox Code Playgroud)
对于那些关注代码许可条款的人来说,这里有一个链接到非常相似的代码(只是在线程上增加了对文化的支持),这些代码有注释表明它是由MIT许可的. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
Lee*_*ith 134
async Main现在是C#7.2的一部分,可以在项目高级构建设置中启用.
对于C#<7.2,正确的方法是:
static void Main(string[] args)
{
MainAsync().GetAwaiter().GetResult();
}
static async Task MainAsync()
{
/*await stuff here*/
}
Run Code Online (Sandbox Code Playgroud)
您将在许多Microsoft文档中看到这种情况,例如:https: //docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-主题的订阅
Des*_*tar 49
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
}
Run Code Online (Sandbox Code Playgroud)
您将'await'关键字读作"启动此长时间运行的任务,然后将控制权返回给调用方法".一旦长时间运行的任务完成,它就会执行后面的代码.await之后的代码类似于以前的CallBack方法.逻辑流程的最大区别不在于中断,这使得编写和读取更加容易.
NSt*_*uke 36
我不是百分百肯定,但我相信这个博客中描述的技术应该适用于很多情况:
因此,
task.GetAwaiter().GetResult()
如果要直接调用此传播逻辑,则可以使用.
Rob*_*t J 23
最常被接受的答案并不完全正确.有一种适用于所有情况的解决方案:ad-hoc消息泵(SynchronizationContext).
调用线程将按预期被阻塞,同时仍然确保从异步函数调用的所有continuation都不会死锁,因为它们将被封送到在调用线程上运行的ad-hoc 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();
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
AsyncPump.Run(() => FooAsync(...));
Run Code Online (Sandbox Code Playgroud)
有关异步泵的更多详细说明,请点击此处.
pan*_*wel 13
这是最简单的解决方案。我在互联网上的某个地方看到过,我不记得在哪里了,但我一直在成功地使用它。它不会使调用线程死锁。
void SynchronousFunction()
{
Task.Run(Foo).Wait();
}
string SynchronousFunctionReturnsString()
{
return Task.Run(Foo).Result;
}
string SynchronousFunctionReturnsStringWithParam(int id)
{
return Task.Run(() => Foo(id)).Result;
}
Run Code Online (Sandbox Code Playgroud)
Hub*_*kul 11
斯蒂芬·克利里的回答;
\n\n\n该方法不应导致死锁(假设ProblemMethodAsync 不会向 UI 线程或类似的任何内容发送更新)。它确实假设 ProblemMethodAsync 可以在线程池线程上调用,但情况并非总是如此。
\n
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
\n这是方法;
\n\n\n线程池黑客与阻塞黑客类似的方法是将异步工作卸载到线程池,然后阻塞\n结果任务。使用此 hack 的代码将类似于图 7 中所示的代码。
\n图 7 线程池黑客代码
\nC#
\n
public sealed class WebDataService : IDataService\n{\n public string Get(int id)\n {\n return Task.Run(() => GetAsync(id)).GetAwaiter().GetResult();\n }\n public async Task<string> GetAsync(int id)\n {\n using (var client = new WebClient())\n return await client.DownloadStringTaskAsync(\n "https://www.example.com/api/values/" + id);\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n\n对 Task.Run 的调用在线程池线程上执行异步方法。这里它将在没有上下文的情况下运行,从而避免\n死锁。这种方法的问题之一是异步方法可能依赖于在特定上下文中执行。因此,它\n\xe2\x80\x99 不能使用 UI 元素或 ASP.NET HttpContext.Current。
\n
对于任何关注这个问题的人来说...
如果您查看的Microsoft.VisualStudio.Services.WebApi
是一个名为的类TaskExtensions
。在该类中,您将看到静态扩展方法Task.SyncResult()
,该方法完全像阻塞线程一样,直到任务返回。
在内部,它调用起来task.GetAwaiter().GetResult()
很简单,但是在任何async
可以返回的方法上工作Task
,Task<T>
或者Task<HttpResponseMessage>
...语法糖,宝贝...爸爸都很喜欢吃甜食。
看起来...GetAwaiter().GetResult()
是在阻塞上下文中执行异步代码的MS官方方法。对于我的用例来说似乎工作得很好。
您可以从同步代码中调用任何异步方法,也就是说,直到您需要调用await
它们为止,在这种情况下,它们也必须标记为async
。
正如很多人在这里建议的那样,您可以Wait()
在同步方法中对结果任务调用或 Result ,但最终会在该方法中进行阻塞调用,这有点违背了异步的目的。
如果您真的无法创建您的方法async
并且您不想锁定同步方法,那么您将不得不通过将其作为参数传递给ContinueWith()
任务上的方法来使用回调方法。
小智 6
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);
OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)
或使用此:
var result=result.GetAwaiter().GetResult().AccessToken
Run Code Online (Sandbox Code Playgroud)
我知道我来晚了。但是,如果像我这样的人想要以一种整洁,简单的方式解决此问题,并且无需依赖其他库。
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();
}
Run Code Online (Sandbox Code Playgroud)
那么你可以这样称呼它
var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());
Run Code Online (Sandbox Code Playgroud)
受其他一些答案的启发,我创建了以下简单的辅助方法:
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();
}
Run Code Online (Sandbox Code Playgroud)
它们可以按如下方式调用(取决于您是否返回值):
RunSync(() => Foo());
var result = RunSync(() => FooWithResult());
Run Code Online (Sandbox Code Playgroud)
请注意,原始问题中的签名public async void Foo()
不正确。它应该是public async Task Foo()
因为您应该为不返回值的异步方法返回 Task not void (是的,有一些罕见的例外)。
归档时间: |
|
查看次数: |
540564 次 |
最近记录: |