同步调用异步方法
Posted
技术标签:
【中文标题】同步调用异步方法【英文标题】:Calling async method synchronously 【发布时间】:2014-03-25 07:34:14 【问题描述】:我有一个async
方法:
public async Task<string> GenerateCodeAsync()
string code = await GenerateCodeService.GenerateCodeAsync();
return code;
我需要从同步方法中调用此方法。
我怎样才能做到这一点而不必复制GenerateCodeAsync
方法以使其同步工作?
更新
尚未找到合理的解决方案。
不过,我看到HttpClient
已经实现了这个模式
using (HttpClient client = new HttpClient())
// async
HttpResponseMessage responseAsync = await client.GetAsync(url);
// sync
HttpResponseMessage responseSync = client.GetAsync(url).Result;
【问题讨论】:
How would I run an async Task<T> method synchronously? 的可能重复项 我希望有一个更简单的解决方案,认为 asp.net 处理这比编写这么多行代码要容易得多 为什么不直接采用异步代码呢?理想情况下,您需要更多的异步代码,而不是更少。 [为什么不直接采用异步代码?] 哈,可能正是因为采用异步代码,他们需要这个解决方案,因为项目的大部分都被转换了!你不可能在一天内重建罗马。 @NicholasPetersen 有时第 3 方库会强迫您这样做。在 FluentValidation 之外的 WithMessage 方法中构建动态消息的示例。由于库设计,没有用于此的异步 API - WithMessage 重载是静态的。将动态参数传递给 WithMessage 的其他方法很奇怪。 【参考方案1】:您可以访问任务的Result
属性,这将导致您的线程阻塞,直到结果可用:
string code = GenerateCodeAsync().Result;
注意:在某些情况下,这可能会导致死锁:您对Result
的调用会阻塞主线程,从而阻止异步代码的其余部分执行。您可以通过以下选项来确保不会发生这种情况:
Add .ConfigureAwait(false)
to your library method或
在线程池线程中显式执行您的异步方法并等待它完成:
string code = Task.Run(() => GenerateCodeAsync).Result;
这并不意味着您应该在所有异步调用之后无意识地添加.ConfigureAwait(false)
!有关为什么以及何时应该使用.ConfigureAwait(false)
的详细分析,请参阅以下博客文章:
【讨论】:
如果调用result
存在死锁风险,那么何时 可以安全地获得结果?是否每个异步调用都需要Task.Run
或ConfigureAwait(false)
?
ASP.NET 中没有“主线程”(与 GUI 应用程序不同),但由于how AspNetSynchronizationContext.Post
序列化异步延续,死锁仍然可能:Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action)); _lastScheduledTask = newTask;
@RobertHarvey:如果你无法控制你阻塞的异步方法的实现,那么是的,你应该用Task.Run
包装它以保证安全。或者使用WithNoContext
之类的东西来减少冗余线程切换。
注意:如果调用者本身在线程池中,调用 .Result
仍然会死锁。假设线程池大小为 32,并且有 32 个任务正在运行,Wait()/Result
正在等待一个尚未计划的第 33 个任务,该任务希望在其中一个等待线程上运行。
@Warty - 澄清一下,为了发生死锁,线程池必须重新使用等待结果的同一个线程。这种情况很少发生 - 这很糟糕,因为当它发生时,问题出在哪里并不明显。【参考方案2】:
你应该得到等待者(GetAwaiter()
)并结束等待异步任务完成(GetResult()
)。
string code = GenerateCodeAsync().GetAwaiter().GetResult();
【讨论】:
我们在使用此解决方案时遇到了死锁。请注意。 MSDNTask.GetAwaiter
:此方法旨在供编译器使用,而不是在应用程序代码中使用。
我仍然收到错误对话框弹出窗口(违背我的意愿),带有“切换到”或“重试”按钮......。但是,调用实际上会执行并返回正确的响应。
@Oliver,我刚刚看到这篇文章,想了解为什么我在 WebForms 中的应用程序在我有上面的代码时会死锁。这确实会导致死锁。【参考方案3】:
您应该能够使用委托、lambda 表达式来完成这项工作
private void button2_Click(object sender, EventArgs e)
label1.Text = "waiting....";
Task<string> sCode = Task.Run(async () =>
string msg =await GenerateCodeAsync();
return msg;
);
label1.Text += sCode.Result;
private Task<string> GenerateCodeAsync()
return Task.Run<string>(() => GenerateCode());
private string GenerateCode()
Thread.Sleep(2000);
return "I m back" ;
【讨论】:
这个 sn-p 不会编译。 Task.Run 的返回类型是 Task。有关完整说明,请参阅此MSDN blog。 感谢指出,是的,它返回任务类型。将“string sCode”替换为 TaskTask.Run
包装调用 - 从而将其移动到线程池。但是,根据其他答案和 cmets,这并不能确保它永远死锁 - 它可能只会使死锁“罕见” - 因此更难追查出了什么问题。【参考方案4】:
我需要从同步方法中调用此方法。
正如另一个答案所暗示的那样,GenerateCodeAsync().Result
或 GenerateCodeAsync().Wait()
是可能的。这将阻塞当前线程,直到 GenerateCodeAsync
完成。
但是,您的问题被标记为asp.net,并且您还留下了评论:
我希望有一个更简单的解决方案,认为 asp.net 可以处理 这比写这么多行代码要容易得多
我的意思是,您不应该阻塞 ASP.NET 中的异步方法。这将降低您的 Web 应用程序的可扩展性,并可能造成死锁(当 GenerateCodeAsync
中的 await
延续发布到 AspNetSynchronizationContext
时)。使用 Task.Run(...).Result
将某些内容卸载到池线程然后阻塞将进一步损害可伸缩性,因为它会导致 +1 个线程来处理给定的 HTTP 请求。
ASP.NET 内置了对异步方法的支持,可以通过异步控制器(在 ASP.NET MVC 和 Web API 中)或直接通过经典 ASP.NET 中的AsyncManager
和PageAsyncTask
。你应该使用它。更多详情,请查看this answer。
【讨论】:
我正在覆盖DbContext
的 SaveChanges()
方法,在这里我正在调用异步方法,所以不幸的是异步控制器在这种情况下无法帮助我
@RaraituL,一般来说,你不要混合异步和同步代码,选择 euther 模型。您可以同时实现 SaveChangesAsync
和 SaveChanges
,只要确保它们不会在同一个 ASP.NET 项目中同时被调用。
并非所有.NET MVC
过滤器都支持异步代码,例如IAuthorizationFilter
,所以我不能一直使用async
@Noseratio 这是一个不切实际的目标。具有异步和同步代码的库太多,以及无法仅使用一种模型的情况。例如,MVC ActionFilters 不支持异步代码。
@Noserato,问题是关于从同步调用异步方法。有时您无法更改您实现的 API。假设您从某个 3-rd 方框架“A”实现了一些同步接口(您不能将框架重写为异步方式),但是您尝试在实现中使用的 3rd 方库“B”只有异步。生成的产品也是库,可以在任何地方使用,包括 ASP.NET 等。【参考方案5】:
Microsoft Identity 具有同步调用异步方法的扩展方法。 例如有 GenerateUserIdentityAsync() 方法和等于 CreateIdentity()
如果您查看 UserManagerExtensions.CreateIdentity() 它看起来像这样:
public static ClaimsIdentity CreateIdentity<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user,
string authenticationType)
where TKey : IEquatable<TKey>
where TUser : class, IUser<TKey>
if (manager == null)
throw new ArgumentNullException("manager");
return AsyncHelper.RunSync(() => manager.CreateIdentityAsync(user, authenticationType));
现在让我们看看 AsyncHelper.RunSync 做了什么
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
var cultureUi = CultureInfo.CurrentUICulture;
var culture = CultureInfo.CurrentCulture;
return _myTaskFactory.StartNew(() =>
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = cultureUi;
return func();
).Unwrap().GetAwaiter().GetResult();
所以,这是你对异步方法的封装。 请不要从 Result 中读取数据 - 它可能会阻止您在 ASP 中的代码。
还有一种方式——我觉得很可疑,但你也可以考虑一下
Result r = null;
YourAsyncMethod()
.ContinueWith(t =>
r = t.Result;
)
.Wait();
【讨论】:
您认为您建议的第二种方式存在什么问题? @DavidClarke 可能是在没有锁的情况下从多个线程访问非易失性变量的线程安全问题。【参考方案6】:为了防止死锁,当我必须同步调用@Heinzi 提到的异步方法时,我总是尝试使用Task.Run()
。
但是,如果异步方法使用参数,则必须修改该方法。例如Task.Run(GenerateCodeAsync("test")).Result
给出错误:
参数 1:无法从 '
System.Threading.Tasks.Task<string>
' 转换 到'System.Action'
可以这样调用:
string code = Task.Run(() => GenerateCodeAsync("test")).Result;
【讨论】:
【参考方案7】:此线程上的大多数答案要么很复杂,要么会导致死锁。
下面的方法很简单,它可以避免死锁,因为我们正在等待任务完成,然后才得到它的结果-
var task = Task.Run(() => GenerateCodeAsync());
task.Wait();
string code = task.Result;
此外,这是对 MSDN 文章的参考,其中谈到了完全相同的事情- https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result-in-main-context/
【讨论】:
尽管 Microsoft 引用了它的Task.Run
,但它使这项工作(在 ASP.NET 上下文线程上运行时)。 Wait
无关紧要 - Result
在任务未完成时执行等效操作。见this SO Q&A。
@ToolmakerSteve 我不同意你的看法。我看了this reference,两者之间的代码不一样。直接从任务中提取 .Result 可能会随机导致死锁。我知道,我遇到了这个问题。当它发生时很难排除故障。
我不得不说,当您无法使调用方法异步时,这是最正确的解决方案。最好的答案是让整个流程异步。
@FrankThomas - 查看***.com/a/35948021/199364 中提到的源参考,Task.Wait
测试IsWaitNotificationEnabledOrNotRanToCompletion
,然后调用InternalWait
。 Future.Result
测试IsWaitNotificationEnabledOrNotRanToCompletion
,然后调用GetResultCore
,它执行if (!IsCompleted) InternalWait
。我看不出有什么区别,w.r.t。潜在的死锁。当然,它不可能证明没有死锁,并且代码的任何更改都会改变时间,因此完全有可能让一种方法随机失败,而另一种方法工作......直到它没有。
(我的意思是不可能证明缺席,除非缺席可以通过设计或静态分析来证明;我的观点是,尽管 Result 确实存在,但您无法确定 Wait 不会导致,因为显示的实现。)【参考方案8】:
我正在使用这种方法,它还将处理和传播来自底层异步任务的异常。
private string RunSync()
var task = Task.Run(async () => await GenerateCodeService.GenerateCodeAsync());
if (task.IsFaulted && task.Exception != null)
throw task.Exception;
return task.Result;
【讨论】:
【参考方案9】:一些扩展方法如何异步等待异步操作完成,然后设置一个ManualResetEvent来表示完成。
注意:您可以使用 Task.Run(),但扩展方法是表达您真正想要的更简洁的接口。
展示如何使用扩展的测试:
[TestClass]
public class TaskExtensionsTests
[TestMethod]
public void AsynchronousOperationWithNoResult()
SampleAsynchronousOperationWithNoResult().AwaitResult();
[TestMethod]
public void AsynchronousOperationWithResult()
Assert.AreEqual(3, SampleAsynchronousOperationWithResult(3).AwaitResult());
[TestMethod]
[ExpectedException(typeof(Exception))]
public void AsynchronousOperationWithNoResultThrows()
SampleAsynchronousOperationWithNoResultThrows().AwaitResult();
[TestMethod]
[ExpectedException(typeof(Exception))]
public void AsynchronousOperationWithResultThrows()
SampleAsynchronousOperationWithResultThrows(3).AwaitResult();
private static async Task SampleAsynchronousOperationWithNoResult()
await Task.Yield();
private static async Task<T> SampleAsynchronousOperationWithResult<T>(T result)
await Task.Yield();
return result;
private static async Task SampleAsynchronousOperationWithNoResultThrows()
await Task.Yield();
throw new Exception();
private static async Task<T> SampleAsynchronousOperationWithResultThrows<T>(T result)
await Task.Yield();
throw new Exception();
[TestMethod]
public void AsynchronousValueOperationWithNoResult()
SampleAsynchronousValueOperationWithNoResult().AwaitResult();
[TestMethod]
public void AsynchronousValueOperationWithResult()
Assert.AreEqual(3, SampleAsynchronousValueOperationWithResult(3).AwaitResult());
[TestMethod]
[ExpectedException(typeof(Exception))]
public void AsynchronousValueOperationWithNoResultThrows()
SampleAsynchronousValueOperationWithNoResultThrows().AwaitResult();
[TestMethod]
[ExpectedException(typeof(Exception))]
public void AsynchronousValueOperationWithResultThrows()
SampleAsynchronousValueOperationWithResultThrows(3).AwaitResult();
private static async ValueTask SampleAsynchronousValueOperationWithNoResult()
await Task.Yield();
private static async ValueTask<T> SampleAsynchronousValueOperationWithResult<T>(T result)
await Task.Yield();
return result;
private static async ValueTask SampleAsynchronousValueOperationWithNoResultThrows()
await Task.Yield();
throw new Exception();
private static async ValueTask<T> SampleAsynchronousValueOperationWithResultThrows<T>(T result)
await Task.Yield();
throw new Exception();
扩展
/// <summary>
/// Defines extension methods for <see cref="Task"/> and <see cref="ValueTask"/>.
/// </summary>
public static class TaskExtensions
/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking; ignoring cancellation.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the pending operation.
/// </param>
public static void AwaitCompletion(this ValueTask task)
new SynchronousAwaiter(task, true).GetResult();
/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking; ignoring cancellation.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the pending operation.
/// </param>
public static void AwaitCompletion(this Task task)
new SynchronousAwaiter(task, true).GetResult();
/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the pending operation.
/// </param>
/// <typeparam name="T">
/// The result type of the operation.
/// </typeparam>
/// <returns>
/// The result of the operation.
/// </returns>
public static T AwaitResult<T>(this Task<T> task)
return new SynchronousAwaiter<T>(task).GetResult();
/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the pending operation.
/// </param>
public static void AwaitResult(this Task task)
new SynchronousAwaiter(task).GetResult();
/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking.
/// </summary>
/// <param name="task">
/// The <see cref="ValueTask"/> representing the pending operation.
/// </param>
/// <typeparam name="T">
/// The result type of the operation.
/// </typeparam>
/// <returns>
/// The result of the operation.
/// </returns>
public static T AwaitResult<T>(this ValueTask<T> task)
return new SynchronousAwaiter<T>(task).GetResult();
/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking.
/// </summary>
/// <param name="task">
/// The <see cref="ValueTask"/> representing the pending operation.
/// </param>
public static void AwaitResult(this ValueTask task)
new SynchronousAwaiter(task).GetResult();
/// <summary>
/// Ignore the <see cref="OperationCanceledException"/> if the operation is cancelled.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the asynchronous operation whose cancellation is to be ignored.
/// </param>
/// <returns>
/// The <see cref="Task"/> representing the asynchronous operation whose cancellation is ignored.
/// </returns>
public static async Task IgnoreCancellationResult(this Task task)
try
await task.ConfigureAwait(false);
catch (OperationCanceledException)
/// <summary>
/// Ignore the <see cref="OperationCanceledException"/> if the operation is cancelled.
/// </summary>
/// <param name="task">
/// The <see cref="ValueTask"/> representing the asynchronous operation whose cancellation is to be ignored.
/// </param>
/// <returns>
/// The <see cref="ValueTask"/> representing the asynchronous operation whose cancellation is ignored.
/// </returns>
public static async ValueTask IgnoreCancellationResult(this ValueTask task)
try
await task.ConfigureAwait(false);
catch (OperationCanceledException)
/// <summary>
/// Ignore the results of an asynchronous operation allowing it to run and die silently in the background.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the asynchronous operation whose results are to be ignored.
/// </param>
public static async void IgnoreResult(this Task task)
try
await task.ConfigureAwait(false);
catch
// ignore exceptions
/// <summary>
/// Ignore the results of an asynchronous operation allowing it to run and die silently in the background.
/// </summary>
/// <param name="task">
/// The <see cref="ValueTask"/> representing the asynchronous operation whose results are to be ignored.
/// </param>
public static async void IgnoreResult(this ValueTask task)
try
await task.ConfigureAwait(false);
catch
// ignore exceptions
/// <summary>
/// Internal class for waiting for asynchronous operations that have a result.
/// </summary>
/// <typeparam name="TResult">
/// The result type.
/// </typeparam>
public class SynchronousAwaiter<TResult>
/// <summary>
/// The manual reset event signaling completion.
/// </summary>
private readonly ManualResetEvent manualResetEvent;
/// <summary>
/// The exception thrown by the asynchronous operation.
/// </summary>
private Exception exception;
/// <summary>
/// The result of the asynchronous operation.
/// </summary>
private TResult result;
/// <summary>
/// Initializes a new instance of the <see cref="SynchronousAwaiterTResult"/> class.
/// </summary>
/// <param name="task">
/// The task representing an asynchronous operation.
/// </param>
public SynchronousAwaiter(Task<TResult> task)
this.manualResetEvent = new ManualResetEvent(false);
this.WaitFor(task);
/// <summary>
/// Initializes a new instance of the <see cref="SynchronousAwaiterTResult"/> class.
/// </summary>
/// <param name="task">
/// The task representing an asynchronous operation.
/// </param>
public SynchronousAwaiter(ValueTask<TResult> task)
this.manualResetEvent = new ManualResetEvent(false);
this.WaitFor(task);
/// <summary>
/// Gets a value indicating whether the operation is complete.
/// </summary>
public bool IsComplete => this.manualResetEvent.WaitOne(0);
/// <summary>
/// Synchronously get the result of an asynchronous operation.
/// </summary>
/// <returns>
/// The result of the asynchronous operation.
/// </returns>
public TResult GetResult()
this.manualResetEvent.WaitOne();
return this.exception != null ? throw this.exception : this.result;
/// <summary>
/// Tries to synchronously get the result of an asynchronous operation.
/// </summary>
/// <param name="operationResult">
/// The result of the operation.
/// </param>
/// <returns>
/// The result of the asynchronous operation.
/// </returns>
public bool TryGetResult(out TResult operationResult)
if (this.IsComplete)
operationResult = this.exception != null ? throw this.exception : this.result;
return true;
operationResult = default;
return false;
/// <summary>
/// Background "thread" which waits for the specified asynchronous operation to complete.
/// </summary>
/// <param name="task">
/// The task.
/// </param>
private async void WaitFor(Task<TResult> task)
try
this.result = await task.ConfigureAwait(false);
catch (Exception exception)
this.exception = exception;
finally
this.manualResetEvent.Set();
/// <summary>
/// Background "thread" which waits for the specified asynchronous operation to complete.
/// </summary>
/// <param name="task">
/// The task.
/// </param>
private async void WaitFor(ValueTask<TResult> task)
try
this.result = await task.ConfigureAwait(false);
catch (Exception exception)
this.exception = exception;
finally
this.manualResetEvent.Set();
/// <summary>
/// Internal class for waiting for asynchronous operations that have no result.
/// </summary>
public class SynchronousAwaiter
/// <summary>
/// The manual reset event signaling completion.
/// </summary>
private readonly ManualResetEvent manualResetEvent = new ManualResetEvent(false);
/// <summary>
/// The exception thrown by the asynchronous operation.
/// </summary>
private Exception exception;
/// <summary>
/// Initializes a new instance of the <see cref="SynchronousAwaiterTResult"/> class.
/// </summary>
/// <param name="task">
/// The task representing an asynchronous operation.
/// </param>
/// <param name="ignoreCancellation">
/// Indicates whether to ignore cancellation. Default is false.
/// </param>
public SynchronousAwaiter(Task task, bool ignoreCancellation = false)
this.manualResetEvent = new ManualResetEvent(false);
this.WaitFor(task, ignoreCancellation);
/// <summary>
/// Initializes a new instance of the <see cref="SynchronousAwaiterTResult"/> class.
/// </summary>
/// <param name="task">
/// The task representing an asynchronous operation.
/// </param>
/// <param name="ignoreCancellation">
/// Indicates whether to ignore cancellation. Default is false.
/// </param>
public SynchronousAwaiter(ValueTask task, bool ignoreCancellation = false)
this.manualResetEvent = new ManualResetEvent(false);
this.WaitFor(task, ignoreCancellation);
/// <summary>
/// Gets a value indicating whether the operation is complete.
/// </summary>
public bool IsComplete => this.manualResetEvent.WaitOne(0);
/// <summary>
/// Synchronously get the result of an asynchronous operation.
/// </summary>
public void GetResult()
this.manualResetEvent.WaitOne();
if (this.exception != null)
throw this.exception;
/// <summary>
/// Background "thread" which waits for the specified asynchronous operation to complete.
/// </summary>
/// <param name="task">
/// The task.
/// </param>
/// <param name="ignoreCancellation">
/// Indicates whether to ignore cancellation. Default is false.
/// </param>
private async void WaitFor(Task task, bool ignoreCancellation)
try
await task.ConfigureAwait(false);
catch (OperationCanceledException)
catch (Exception exception)
this.exception = exception;
finally
this.manualResetEvent.Set();
/// <summary>
/// Background "thread" which waits for the specified asynchronous operation to complete.
/// </summary>
/// <param name="task">
/// The task.
/// </param>
/// <param name="ignoreCancellation">
/// Indicates whether to ignore cancellation. Default is false.
/// </param>
private async void WaitFor(ValueTask task, bool ignoreCancellation)
try
await task.ConfigureAwait(false);
catch (OperationCanceledException)
catch (Exception exception)
this.exception = exception;
finally
this.manualResetEvent.Set();
【讨论】:
【参考方案10】:编辑:
Task 有 Wait 方法,Task.Wait(),它等待“promise”解决然后继续,从而使其同步。 示例:
async Task<String> MyAsyncMethod() ...
String mySyncMethod()
return MyAsyncMethod().Wait();
【讨论】:
请详细说明您的答案。它是如何使用的?它对回答问题有多大帮助?【参考方案11】:我更喜欢非阻塞方法:
Dim aw1=GenerateCodeAsync().GetAwaiter()
While Not aw1.IsCompleted
Application.DoEvents()
End While
【讨论】:
【参考方案12】:如果您有一个名为“RefreshList”的异步方法,那么您可以从如下所示的非异步方法中调用该异步方法。
Task.Run(async () => await RefreshList(); );
【讨论】:
以上是关于同步调用异步方法的主要内容,如果未能解决你的问题,请参考以下文章