【问题标题】:The async method lacks await异步方法缺少等待
【发布时间】:2022-02-01 16:29:54
【问题描述】:

我在这个主题上找到了很多,但似乎没有一个例子/答案能解决我们的问题。我们不断收到警告说下面的方法(和其他类似的方法)缺少等待,但是当我们尝试添加等待时,我们得到“不能等待这个”(无论我们尝试添加等待的地方)。我们做错了什么?

protected async Task<T> PerformGet<T>(string requestUri, bool appendHeader)
        {
            try
            {
                _webApiClient = new HttpClient { BaseAddress = _baseAddress };
                if (appendHeader) AppendDefaultHeaders();
                var webApiResponse = Task.Run(() => _webApiClient.GetAsync(requestUri)).Result;
                var responseContent = webApiResponse.Content.ReadAsStringAsync().Result;
                return JsonConvert.DeserializeObject<T>(responseContent);
            } 
            catch (Exception ex) {
                throw ex;
            }
        }

非常感谢任何帮助!

【问题讨论】:

  • 每个答案都离开了Task.Run ...

标签: c# asynchronous asp.net-web-api


【解决方案1】:

async 是一个关键字,用于将方法标记为编译器完全重写以支持 async/await 模式的候选方法。

发生的情况是,该方法在您在内部使用 await 关键字的每个点都被拆分。如果您将方法标记为async 但不使用await,它将抱怨您将其标记为不必要,或者您忘记使用await。所以这就是你得到编译器错误的原因。

现在,下一步是将await 添加到您的方法中,但这意味着稍微更改它:

  • 为了运行一项任务而启动另一项任务太过分了
  • 在任务上使用.Result 很容易出现死锁,这就是await 派上用场的地方

所以让我们重写你的方法。首先我们去掉对Task.Result 的调用并替换为await

protected async Task<T> PerformGet<T>(string requestUri, bool appendHeader)
{
    try
    {
        _webApiClient = new HttpClient { BaseAddress = _baseAddress };
        if (appendHeader) AppendDefaultHeaders();
        var webApiResponse = await Task.Run(() => _webApiClient.GetAsync(requestUri));
        var responseContent = await webApiResponse.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(responseContent);
    } 
    catch (Exception ex) {
        throw ex;
    }
}

然后我们去掉不必要的task-in-task:

protected async Task<T> PerformGet<T>(string requestUri, bool appendHeader)
{
    try
    {
        _webApiClient = new HttpClient { BaseAddress = _baseAddress };
        if (appendHeader) AppendDefaultHeaders();
        var webApiResponse = await _webApiClient.GetAsync(requestUri);
        var responseContent = await webApiResponse.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(responseContent);
    } 
    catch (Exception ex) {
        throw ex;
    }
}

然后我们去掉不必要的try/catch:

protected async Task<T> PerformGet<T>(string requestUri, bool appendHeader)
{
    _webApiClient = new HttpClient { BaseAddress = _baseAddress };
    if (appendHeader) AppendDefaultHeaders();
    var webApiResponse = await _webApiClient.GetAsync(requestUri);
    var responseContent = await webApiResponse.Content.ReadAsStringAsync();
    return JsonConvert.DeserializeObject<T>(responseContent);
}

然后我们将HttpClient 对象放入using 语句中(参见下面的注释):

protected async Task<T> PerformGet<T>(string requestUri, bool appendHeader)
{
    using (var client = new HttpClient { BaseAddress = _baseAddress })
    {
        if (appendHeader) AppendDefaultHeaders(client);
        var response = await client.GetAsync(requestUri);
        var responseContent = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(responseContent);
    }
}

这删除了字段,这在进行异步工作时通常是一个坏主意,并使其成为此方法的本地概念。这也需要更改 AppendDefaultHeaders 方法以接受客户端应将标头作为参数添加到其中。


注意关于HttpClient。虽然上面的代码把它放在using 子句中,这是因为原始代码构造了一个实例,而HttpClient 实现了IDisposable

但是,您不应该这样做,而应该重用 HttpClient 实例。您可以在这里阅读更多相关信息,You're using HttpClient wrong and it's destabilizing your software

【讨论】:

  • 非常感谢以上内容。当我尝试在公共异步 Task GetCompanyByID(int id) { try { var response = PerformGet(string.Format("api/Companies/{ 0}",id.ToString()));返回响应; } catch (Exception ex) { throw ex; } }。我们只是不明白我们缺少什么。
  • 里面没有等待吗?
  • var response = await PerformGet&lt;...&gt;(...);
  • 每当我尝试添加等待时。它告诉我我不能等待 。我有 var response = await PerformGet<...> 它说我不能这样做。
  • 如果要使用await,必须将其标记为async。如果您不打算使用await,请不要将其标记为async
【解决方案2】:

您已将方法标记为async,但未使用await。您的方法没有理由成为async。您可以将定义重写为

protected T PerformGet<T>(string requestUri, bool appendHeader)

async 本质上是说'在这个方法中,我们要去await 一个异步调用'

【讨论】:

  • 我们需要所有的方法都是异步的。调用它的方法是 public async Task GetCompanyByID(int id) { try { var response = PerformGet(string.Format("api/Companies/{0}",id.ToString()));返回响应; } catch (Exception ex) { throw ex; } }
  • 我们也得到了一个“缺乏等待”的方法。
  • 他想用async,那他为什么要让它成为一个普通的方法呢?他正在做网络请求,所以它真的应该是异步的
  • @RaniRadcliff 如果您认为所有方法都必须是异步的,您就误解了async 的含义。您仍然可以使用我提供的定义调用此方法,无论您是否更改定义,它都是同步的。警告本质上是说“嘿,您将此方法标记为async,但它确实是同步的”。
  • @AlexanderDerck 因为该方法在任何情况下都是同步的。没有awaitasync 编译器指令将被忽略(并因此生成警告)
【解决方案3】:

原因是您没有在等待任何事情,因为您在异步调用结束时拥有 .Result。试试这个

protected async Task<T> PerformGet<T>(string requestUri, bool appendHeader)    
{
    try
    {
        _webApiClient = new HttpClient { BaseAddress = _baseAddress };
        if (appendHeader) AppendDefaultHeaders();
        var webApiResponse = await Task.Run(() => _webApiClient.GetAsync(requestUri));
        var responseContent = await webApiResponse.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(responseContent);
    } 
    catch (Exception ex) {
        throw ex;
    }
}

【讨论】:

  • 我知道你只是在标记他的代码,但是这个代码:await Task.Run(() =&gt; _webApiClient.GetAsync(requestUri)); 是没有意义的。你正在启动一个任务,它除了启动另一个任务之外什么都不做。
  • 是的。这段代码不是我写的。办公室里的每个人都是新手。我们只是想弄清楚如何做到这一点,以使其正常工作。
  • 好地方,我根本没注意到。我实际上很惊讶返回结果,但编译器似乎可以等待内部任务
  • 异步非常适合 IO 操作,无论是文件还是网络,因为在幕后工作完全脱离了 CPU。在多线程中,您创建另一个线程来等待 IO 工作完成。使用异步根本没有后台线程,因为工作从 CPU 转移到 I/O 芯片上。当工作完成时,会向 CPU 发出一个中断,并且无需详细说明,工作就会返回到启动它的线程。 Node.JS 使用这种模式并且实际上在单个线程上运行,这也是它如此快速和可扩展的部分原因。
【解决方案4】:

您不能同时使用await.Result。这是一个或另一个。 Result 将阻塞你的线程,直到异步方法完成,就像它是一个同步方法一样。 await 表示在方法运行时线程将被移回线程池,当方法完成时,它将抓取另一个线程以继续执行其余的功能。所以你的功能应该是:

protected async Task<T> PerformGet<T>(string requestUri, bool appendHeader)
    {
        try
        {
            _webApiClient = new HttpClient { BaseAddress = _baseAddress };
            if (appendHeader) AppendDefaultHeaders();
            var webApiResponse = await Task.Run(() => _webApiClient.GetAsync(requestUri));
            var responseContent = await webApiResponse.Content.ReadAsStringAsync().;
            return JsonConvert.DeserializeObject<T>(responseContent);
        } 
        catch (Exception ex) {
            throw ex;
        }
    }

另外你的Task.Run(...) 有点没意义,用var webApiResponse = await _webApiClient.GetAsync(requestUri));

【讨论】:

  • 我还要指出,使用.Result 是危险的,根据上下文可能会导致死锁。
  • 删除 .Result 并添加 await Task.Run 会从中删除警告,但调用 Perform Get 的方法会给我们一个“缺少等待”警告。该方法是 public async Task GetCompanyByID(int id) { try { var response = PerformGet(string.Format("api/Companies/{0}",id.ToString()));返回响应; } catch (Exception ex) { throw ex; } }
  • 你也需要 await PerformGet 方法,因为它是异步的,一旦你开始使用 async 你真的必须使用它直到你的代码顶部
【解决方案5】:

你试过这个吗:

protected async Task<T> PerformGet<T>(string requestUri, bool appendHeader)
{
    try
    {
        _webApiClient = new HttpClient { BaseAddress = _baseAddress };
        if (appendHeader) AppendDefaultHeaders();
        var webApiResponse = await Task.Run(() => _webApiClient.GetAsync(requestUri));
        var responseContent = await webApiResponse.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(responseContent);
    } 
    catch (Exception ex) {
        throw ex;
    }
}

【讨论】:

    【解决方案6】:

    基于此:

    var webApiResponse = Task.Run(() =&gt; _webApiClient.GetAsync(requestUri)).Result;

    我建议您退后一步,尝试了解 async await 正在为您做什么。有一点学习曲线,但它会确保你实际上不会因为使用它而使事情变得更糟。

    【讨论】:

      猜你喜欢
      • 2012-10-25
      • 2017-04-30
      • 2017-11-09
      • 1970-01-01
      • 1970-01-01
      • 2019-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多