如何从非异步方法调用异步方法?

Sam*_*nan 19 c# asynchronous async-await

我有以下方法:

    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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

以上不起作用,似乎没有正确返回任何结果.我不知道我在哪里错过强制等待结果的陈述?我希望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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

我错过了什么吗?

注意:出于某种原因,当我断开上面的异步方法时,当它到达"var json = await httpClient.GetStringAsync(SourceURI)"这一行时,它就会超出断点而我无法返回到该方法.

Ste*_*ary 25

我错过了什么吗?

是.异步代码 - 本质上 - 暗示当操作正在进行时不使用当前线程.同步代码 - 就其本质而言 - 意味着在操作进行时当前线程被阻塞.这就是为什么从同步代码中调用异步代码甚至没有意义.事实上,正如我在博客中描述的那样,一种天真的方法(使用Result/ Wait)很容易导致死锁.

首先要考虑的是:我的API 应该是同步还是异步?如果它处理I/O(如本例所示),它应该是异步的.所以,这将是一个更合适的设计:

public async Task<string> RetrieveHolidayDatesFromSourceAsync() {
    var result = await this.DoRetrieveHolidayDatesFromSourceAsync();
    /** Do stuff **/
    var returnedResult  = this.TransformResults(result); /** Where result gets used **/
    return returnedResult;
}
Run Code Online (Sandbox Code Playgroud)

正如我在异步最佳实践文章中所描述的那样,您应该"始终保持异步".如果你不这样做,你无论如何都不会从异步中获得任何好处,所以为什么要这么麻烦?

但是,让我们说你最终会对异步感兴趣,但是现在你无法改变一切,你只想改变应用程序的一部分.这是一个很常见的情况.

在这种情况下,正确的做法是揭露两者同步和异步的API.最终,在升级所有其他代码之后,可以删除同步API.我在关于棕色异步开发的文章中探讨了这种场景的各种选项; 我个人最喜欢的是"bool参数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);
  }
}
Run Code Online (Sandbox Code Playgroud)

这种方法避免了代码重复,并避免了与其他"异步同步"反模式解决方案相同的死锁或重入问题.

请注意,我仍然会将生成的代码视为正确异步API路径上的"中间步骤".特别是,内部代码必须依赖WebClient(支持同步和异步)而不是首选HttpClient(仅支持异步).一旦所有的调用代码都改为使用RetrieveHolidayDatesFromSourceAsync而不是RetrieveHolidayDatesFromSource,那么我会重新审视这一点并删除所有技术债务,将其更改为使用HttpClient并仅为异步.

  • @NickWeaver:不.它不是'async`,所以它不能使用`await`. (3认同)

Ped*_*ias 5

public string RetrieveHolidayDatesFromSource() {
    var result = this.RetrieveHolidayDatesFromSourceAsync().Result;
    /** Do stuff **/
    var returnedResult  = this.TransformResults(result.Result); /** Where result gets used **/
    return returnedResult;
}
Run Code Online (Sandbox Code Playgroud)

如果将.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());

}
Run Code Online (Sandbox Code Playgroud)

解决此意见:此方法没有任何问题。

  • “这没有任何问题”:)...您可能会更好一点,并解释一下在外部控制台应用程序中实际使用时如何处理由此产生的死锁。 (5认同)
  • 如果您在UI环境或任何具有单线程`SynchronizationContext`的环境中运行,可能会有一种情况。在这种情况下,对.Result的调用将阻止进一步执行,但在async方法中的await可能会在稍后被阻止的线程运行期间注册其继续。混合异步和同步等待总是非常危险的,尤其是在UI环境中。 (2认同)