【问题标题】: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) 行时,它刚刚超出断点,我无法返回到该方法。

【问题讨论】:

    标签: c# asynchronous async-await


    【解决方案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&lt;string&gt; 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 的代码都不会出现这种情况。
    猜你喜欢
    • 1970-01-01
    • 2017-03-06
    • 1970-01-01
    • 2020-03-10
    • 1970-01-01
    • 2021-04-18
    • 1970-01-01
    相关资源
    最近更新 更多