如何从非异步方法调用异步方法? [复制]
Posted
技术标签:
【中文标题】如何从非异步方法调用异步方法? [复制]【英文标题】:How Do I Call an Async Method from a Non-Async Method? [duplicate] 【发布时间】:2016-07-20 19:31:39 【问题描述】:我有以下方法:
public string RetrieveHolidayDatesFromSource()
var result = this.RetrieveHolidayDatesFromSourceAsync();
/** Do stuff **/
var returnedResult = this.TransformResults(result.Result); /** Where result gets used **/
return returnedResult;
private async Task<string> RetrieveHolidayDatesFromSourceAsync()
using (var httpClient = new HttpClient())
var json = await httpClient.GetStringAsync(SourceURI);
return json;
上述方法不起作用,似乎没有正确返回任何结果。我不确定我在哪里遗漏了强制等待结果的声明?我希望 RetrieveHolidayDatesFromSource()
方法返回一个字符串。
以下工作正常,但它是同步的,我相信它可以改进?请注意,下面是同步的,我想更改为异步,但由于某种原因无法绕开。
public string RetrieveHolidayDatesFromSource()
var result = this.RetrieveHolidayDatesFromSourceAsync();
/** Do Stuff **/
var returnedResult = this.TransformResults(result); /** This is where Result is actually used**/
return returnedResult;
private string RetrieveHolidayDatesFromSourceAsync()
using (var httpClient = new HttpClient())
var json = httpClient.GetStringAsync(SourceURI);
return json.Result;
我错过了什么吗?
注意:由于某种原因,当我对上述异步方法进行断点时,当它到达var json = await httpClient.GetStringAsync(SourceURI)
行时,它刚刚超出断点,我无法返回到该方法。
【问题讨论】:
【参考方案1】:我错过了什么吗?
是的。异步代码 - 就其性质而言 - 意味着在操作进行时不使用当前线程。同步代码 - 就其性质而言 - 意味着当前线程在操作进行时被阻塞。这就是为什么从同步代码中调用异步代码实际上甚至没有意义。事实上,正如我在博客中所描述的那样,a naive approach (using Result
/Wait
) can easily result in deadlocks。
首先要考虑的是:应该我的 API 是同步的还是异步的?如果它处理 I/O(如本例所示),则为 should be asynchronous。所以,这将是一个更合适的设计:
public async Task<string> RetrieveHolidayDatesFromSourceAsync()
var result = await this.DoRetrieveHolidayDatesFromSourceAsync();
/** Do stuff **/
var returnedResult = this.TransformResults(result); /** Where result gets used **/
return returnedResult;
正如我在async best practices article 中所描述的,您应该“一直异步”。如果你不这样做,无论如何你都不会从异步中获得任何好处,那何必呢?
但是假设您对最终异步化感兴趣,但现在您无法更改所有内容,您只想更改部分 您的应用程序。这是很常见的情况。
在这种情况下,正确的方法是公开同步和异步 API。最终,在所有其他代码都升级后,可以删除同步 API。我在article on brownfield async development 中探索了这种场景的各种选择;我个人最喜欢的是“bool parameter hack”,它看起来像这样:
public string RetrieveHolidayDatesFromSource()
return this.DoRetrieveHolidayDatesFromSourceAsync(sync: true).GetAwaiter().GetResult();
public Task<string> RetrieveHolidayDatesFromSourceAsync()
return this.DoRetrieveHolidayDatesFromSourceAsync(sync: false);
private async Task<string> DoRetrieveHolidayDatesFromSourceAsync(bool sync)
var result = await this.GetHolidayDatesAsync(sync);
/** Do stuff **/
var returnedResult = this.TransformResults(result);
return returnedResult;
private async Task<string> GetHolidayDatesAsync(bool sync)
using (var client = new WebClient())
return sync
? client.DownloadString(SourceURI)
: await client.DownloadStringTaskAsync(SourceURI);
这种方法避免了代码重复,也避免了其他“同步异步”反模式解决方案常见的任何死锁或重入问题。
请注意,我仍会将生成的代码视为正确异步 API 路径上的“中间步骤”。特别是,内部代码不得不依赖WebClient
(它同时支持同步和异步)而不是首选的HttpClient
(它只支持异步)。一旦所有调用代码都更改为使用RetrieveHolidayDatesFromSourceAsync
而不是RetrieveHolidayDatesFromSource
,然后我会重新审视它并删除所有技术债务,将其更改为使用HttpClient
并仅使用异步。
【讨论】:
public Task<string> RetrieveHolidayDatesFromSourceAsync()
是否缺少await
?
@NickWeaver:不,不是async
,所以不能使用await
。
我明白了。我想知道,因为该方法的名称最后是“异步”。为什么不设置为异步?
@NickWeaver:因为它是一个简单的方法,它只是直接返回任务。它仍然是一个异步 API,因此有 Async
后缀,但不使用 async
/await
来实现它。
感谢您的解释。【参考方案2】:
public string RetrieveHolidayDatesFromSource()
var result = this.RetrieveHolidayDatesFromSourceAsync().Result;
/** Do stuff **/
var returnedResult = this.TransformResults(result.Result); /** Where result gets used **/
return returnedResult;
如果你在异步调用中添加.Result,它会执行并等待结果到达,强制同步
更新:
private static string stringTest()
return getStringAsync().Result;
private static async Task<string> getStringAsync()
return await Task.FromResult<string>("Hello");
static void Main(string[] args)
Console.WriteLine(stringTest());
解决评论:这没有任何问题。
【讨论】:
我认为这行不通,因为我已经在方法中有“result.Result”。拥有 this.RetrieveHolidayDatesFromSourceAsync().Result 会将结果更改为非任务对象,这将导致“var returnedResult = this.TransformResults(result.Result)”中的错误。 我更新了我的答案以向您展示工作代码。您可以看到 getStringAsync 是在同步方法(stringTest())中调用的异步方法,我得到所需的输出而没有错误。您遇到了什么错误? “这工作没有任何问题”:)...您可能会更好一点,并解释如何在实际使用外部控制台应用程序时处理导致的死锁。 如果您在 UI 环境或任何具有单线程SynchronizationContext
的环境中运行,可能会有一个。在这种情况下,对.Result
的调用将阻止进一步的执行,但async
方法中的await
可能会在稍后被阻止的线程运行时注册它的继续。混合异步和同步等待总是很危险的,尤其是在 UI 环境中。
它确实“没有任何问题地工作”这一事实有点侥幸,这仅仅是因为当您等待它时返回的任务已经完成。任何使用HttpClient
或WebClient
的代码都不会出现这种情况。以上是关于如何从非异步方法调用异步方法? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
从同步代码调用异步方法并阻塞直到任务完成的正确方法是啥? [复制]
C# 中有没有办法在不使调用者也异步的情况下调用异步方法? [复制]