我有一个公共异步无效Foo()方法,我想从同步方法调用。到目前为止,我从MSDN文档中看到的都是通过异步方法调用异步方法,但我的整个程序并不是用异步方法构建的。
这可能吗?
下面是一个从异步方法调用这些方法的例子: 演练:使用Async和Await访问Web (c#和Visual Basic)
现在我正在研究从sync方法调用这些async方法。
我有一个公共异步无效Foo()方法,我想从同步方法调用。到目前为止,我从MSDN文档中看到的都是通过异步方法调用异步方法,但我的整个程序并不是用异步方法构建的。
这可能吗?
下面是一个从异步方法调用这些方法的例子: 演练:使用Async和Await访问Web (c#和Visual Basic)
现在我正在研究从sync方法调用这些async方法。
您可以从同步代码中调用任何异步方法,直到您需要等待它们,在这种情况下,它们也必须被标记为异步。
正如很多人在这里建议的那样,您可以在同步方法中对结果任务调用Wait()或Result,但最终会在该方法中进行阻塞调用,这在某种程度上违背了异步的目的。
如果你真的不能让你的方法异步,你不想锁定同步方法,那么你将不得不使用一个回调方法,把它作为参数传递给任务上的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’关键字读为“启动这个长时间运行的任务,然后将控制权返回给调用方法”。长时间运行的任务完成后,它将执行后面的代码。await之后的代码类似于过去的CallBack方法。最大的区别是逻辑流没有中断,这使得读写更容易。
异步编程的确是通过代码库“增长”的。它被比作僵尸病毒。最好的解决办法是让它成长,但有时这是不可能的。
我在我的Nito中写了一些类型。用于处理部分异步代码库的AsyncEx库。然而,没有一种解决方案适用于所有情况。
解决方案一
如果您有一个简单的异步方法,不需要同步回上下文,那么您可以使用Task。WaitAndUnwrapException:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
您不想使用Task。等待或任务。结果,因为它们在AggregateException中包装了异常。
该解决方案仅适用于MyAsyncMethod不同步回上下文的情况。换句话说,MyAsyncMethod中的每个await都应该以ConfigureAwait(false)结束。这意味着它不能更新任何UI元素或访问ASP。NET请求上下文。
解决方案B
如果MyAsyncMethod需要同步回它的上下文,那么你可以使用AsyncContext。RunTask提供一个嵌套的上下文:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
*更新4/14/2014:在库的最新版本中,API如下:
var result = AsyncContext.Run(MyAsyncMethod);
(可以使用Task。因为RunTask将传播Task异常)。
你可能需要AsyncContext的原因。RunTask而不是Task。WaitAndUnwrapException是由于在WinForms/WPF/SL/ASP上可能会发生相当微妙的死锁。NET:
A synchronous method calls an async method, obtaining a Task. The synchronous method does a blocking wait on the Task. The async method uses await without ConfigureAwait. The Task cannot complete in this situation because it only completes when the async method is finished; the async method cannot complete because it is attempting to schedule its continuation to the SynchronizationContext, and WinForms/WPF/SL/ASP.NET will not allow the continuation to run because the synchronous method is already running in that context.
这就是为什么在每个异步方法中尽可能使用ConfigureAwait(false)是一个好主意的原因之一。
解决方案C
AsyncContext。RunTask并不适用于所有场景。例如,如果异步方法等待需要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的一篇文章。
我不是100%确定,但我相信这篇博客中描述的技巧在许多情况下都适用:
因此,如果想直接调用这个传播逻辑,可以使用task.GetAwaiter(). getresult()。
微软构建了一个AsyncHelper(内部)类来作为同步运行Async。源代码如下所示:
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();
}
}
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。https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
这与调用Task不一样吗?运行(async ()=> await AsyncFunc()).Result?说真的,微软现在不鼓励调用TaskFactory。StartNew,因为它们都是等效的,一个比另一个更可读。
绝对不是。
答案很简单
.Unwrap().GetAwaiter().GetResult() != .Result
首先
是任务。结果与.GetAwaiter.GetResult()?
其次,unwrap()会导致Task的设置不阻塞被包装的任务。
谁会问这个问题呢
这与调用Task不一样吗?运行(async ()=> await AsyncFunc()).GetAwaiter().GetResult()
那就得看情况了。
关于Task.Start(), Task.Run()和Task.Factory.StartNew()的使用
摘录:
的任务。Run使用taskcreateoptions。DenyChildAttach意思是子任务不能附加到父任务它使用TaskScheduler。默认值,这意味着在线程池中运行任务的线程将始终用于运行任务。 startnew使用TaskScheduler。Current表示当前线程的调度程序,它可能是TaskScheduler。违约但并非总是如此。
更多阅读:
指定同步上下文
ASP。NET Core SynchronizationContext
为了更加安全,像这样调用AsyncHelper不是更好吗?RunSync(async () => await AsyncMethod().ConfigureAwait(false));通过这种方式,我们告诉"内部"方法"请不要尝试同步到上层上下文并取消锁"
非常好的观点,就像大多数对象架构问题一样,这要视情况而定。
作为一个扩展方法,你想强迫绝对每个调用,还是让程序员使用该函数配置他们自己的异步调用?我可以看到call 3场景的用例;它很可能不是你在WPF中想要的,当然在大多数情况下是有意义的,但考虑到在ASP中没有上下文。Net Core如果你能保证它是ASP内部的。Net Core,那就无所谓了。
async Main现在是c# 7.2的一部分,可以在项目的高级构建设置中启用。
对于c# < 7.2,正确的方法是:
static void Main(string[] args)
{
MainAsync().GetAwaiter().GetResult();
}
static async Task MainAsync()
{
/*await stuff here*/
}
你会在很多微软文档中看到这个用法,例如: https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-topics-subscriptions
这些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)。
调用线程将按预期被阻塞,同时仍然确保从async函数调用的所有延续不会死锁,因为它们将被封送到运行在调用线程上的临时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中。运行<>(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
对于任何还在关注这个问题的人…
在微软。visualstudio。services。webapi中有一个叫TaskExtensions的类。在这个类中,你会看到静态扩展方法task . syncresult(),它会完全阻塞线程,直到任务返回。
在内部,它调用Task . getawaiter ().GetResult(),这是非常简单的,但是它重载工作在任何异步方法,返回Task, Task<T>或Task<HttpResponseMessage>…语法糖,宝贝…爸爸爱吃甜食。
getresult()是在阻塞上下文中执行异步代码的ms官方方式。似乎在我的用例中工作得很好。
在尝试了几个小时不同的方法后,或多或少取得了成功,这就是我最终得到的结果。它在获取结果时不会以死锁结束,它还会获取并抛出原始异常,而不是被包装的异常。
private ReturnType RunSync()
{
var task = Task.Run(async () => await myMethodAsync(agency));
if (task.IsFaulted && task.Exception != null)
{
throw task.Exception;
}
return task.Result;
}
受到其他一些答案的启发,我创建了以下简单的帮助方法:
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()中的签名是不正确的。它应该是公共async Task Foo(),因为对于不返回值的异步方法,应该返回Task而不是void(是的,有一些罕见的例外)。
这是最简单的解决办法。我在网上的某个地方看到它,我不记得在哪里了,但我一直在成功地使用它。它不会死锁调用线程。
void SynchronousFunction()
{
Task.Run(Foo).Wait();
}
string SynchronousFunctionReturnsString()
{
return Task.Run(Foo).Result;
}
string SynchronousFunctionReturnsStringWithParam(int id)
{
return Task.Run(() => Foo(id)).Result;
}
斯蒂芬·克利里的回答;
这种方法应该不会导致死锁(假设 ProblemMethodAsync不发送更新到UI线程或任何东西 像这样)。它假设可以在对象上调用ProblemMethodAsync 线程池线程,这并不总是这样。
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
这就是方法;
线程池攻击与阻塞攻击类似的方法是 将异步工作卸载到线程池,然后阻塞 产生的任务。使用此黑客的代码看起来像下面的代码 如图7所示。 图7线程池攻击的代码 c#
public sealed class WebDataService : IDataService
{
public string Get(int id)
{
return Task.Run(() => GetAsync(id)).GetAwaiter().GetResult();
}
public async Task<string> GetAsync(int id)
{
using (var client = new WebClient())
return await client.DownloadStringTaskAsync(
"https://www.example.com/api/values/" + id);
}
}
Task的调用。Run在线程池上执行异步方法 线程。在这里,它将在没有上下文的情况下运行,从而避免 死锁。这种方法的一个问题是异步性 方法不能依赖于在特定上下文中执行。所以,它 不能使用UI元素或ASP。净HttpContext.Current。
现在,您可以使用源生成器使用同步方法生成器库(nuget)创建方法的同步版本。
使用方法如下:
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public async void FooAsync()
它会生成Foo方法,你可以同步调用。