【问题标题】:Async & await always returns "Waiting for completion"异步和等待总是返回“等待完成”
【发布时间】:2019-02-22 09:03:58
【问题描述】:

我有一个应用程序,我在调用 REST Web 服务时使用 Async 和 Await。在运行我的单元测试时,即使我正在使用等待,我似乎也无法得到任何正确的响应。这是异步方法:

public async Task<Response> SendEmail(string apiKey, string senderEmail, string senderName, string recipientEmail, string recipientName, string subject, string content, bool html)
{
    var client = new SendGridClient(apiKey);
    var from = new EmailAddress(senderEmail, senderName);
    var to = new EmailAddress(recipientEmail, recipientName);
    var msg = MailHelper.CreateSingleEmail(from, to, subject, html ? null : content, html ? content : null);
    var response = await client.SendEmailAsync(msg);

    return response;
}

调用方法如下:

public static object SendEmail(string apiKey, string senderEmail, string senderName, string recipientEmail, string recipientName, string subject, string content, bool html)
{
    EmailHandler emailHandler = new EmailHandler();
    var response =  emailHandler.SendEmail(apiKey, senderEmail, senderName, recipientEmail, recipientName, subject, content, html);

    return response;
}

现在,如果我在调用函数的返回响应上设置断点,我可以看到一个状态为“等待激活”且结果为“尚未计算”的对象。在我能够收集到的内容中,对返回的对象调用 .Result 应该使其同步运行并返回结果。 (例如请求的状态码如200)。

我在这里缺少什么?为什么不等到完成?

【问题讨论】:

  • 你需要使SendEmail异步。看来你的僵局了
  • 您从 SendEmail 返回的响应是尚未完成的任务。尝试明确指定类型,而不是使用 objectvar。这样,当您不使用您认为的类型时,您会遇到编译错误。
  • SendEmailAsync 是如何定义的?
  • @GSerg 将object 作为返回类型意味着可以返回ResponseTask&lt;Response&gt;。如果 OP 使该方法返回 Response,那么它将无法编译。至于var,我猜Hans的意思是如果他们使用Response而不是var,那也会产生编译错误。

标签: c# async-await


【解决方案1】:

async-await 只是关键字,它告诉编译器将您的代码编译为 IAsyncStateMachine 的实现并等待它完成,这两个关键字需要结合使用才能起作用,它们只适用于Task 对象。 Task 代表正在进行的工作。

您必须将SendEmail 方法标记为async,将返回类型更改为Task&lt;object&gt;await 您的emailHandler.SendEmail 调用。

底线是,如果你要去async,那么你必须一直去async向上await在一些或多个点,否则你会开始考虑同步运行异步代码,这有点自找麻烦。

public static async Task<object> SendEmail(string apiKey, string senderEmail, string senderName, string recipientEmail, string recipientName, string subject, string content, bool html)
{
    EmailHandler emailHandler = new EmailHandler();
    var response = await emailHandler.SendEmail(apiKey, senderEmail, senderName, recipientEmail, recipientName, subject, content, html);

    return response;
}

一如既往,Stephen Cleary 是获取 async 知识的绝佳来源。

【讨论】:

  • 我明白了。我也使 SendEmail 方法异步 Task 并且在我的单元测试中我也必须等待结果。我相信在一个地方而不是整个链条上等待结果就足够了。
  • 那是因为你需要await这个方法更上一层楼。在第二个建议中,它不等待SendGridClient 发送电子邮件,而是直接返回Task 或在SendGridClient 发送电子邮件的位置工作。这更多取决于选择,我个人喜欢返回任务而不是在较低级别的 async 方法上等待,例如 HttpClient 包装器等。底线是,当您使用 Task 时,您需要等待它完成 - 最好使用 await,否则您将面临性能和可扩展性问题的风险。
  • 好吧,我想我可能明白了。我所说的“块”是指程序需要等待,直到它从长时间运行的代码块中获得结果,因为下一条语句取决于它。
  • 想想如果它与您的单元测试在相同的上下文中,您需要等待 await.Result - 我建议只在单元测试中这样做 - 在您可以断言结果/结果之前。在您的生产代码中,您最终将不得不等待 Task 链完成执行以处理响应。
  • 所有单元测试框架多年来都支持async Test单元测试方法;即使在单元测试中,也无需再阻塞。
【解决方案2】:

因为

var response =  emailHandler.SendEmail(apiKey, senderEmail, senderName, recipientEmail, recipientName, subject, content, html);

启动一个任务,而您的调用者(公共静态对象 SendEmail)不会等待该任务。

Chris Pratt 写了一个 nice blog 并使用 async-helper 来运行 async-methods sync。

【讨论】:

    【解决方案3】:

    在我看来,您希望 SendEmail 是同步的。您可以像这样在 SendEmail 中等待异步方法完成:

    public static Response SendEmail(string apiKey, string senderEmail, string senderName, string recipientEmail, string recipientName, string subject, string content, bool html)
    {
        EmailHandler emailHandler = new EmailHandler();
        var responseTask =  emailHandler.SendEmail(apiKey, senderEmail, senderName, recipientEmail, recipientName, subject, content, html);
        responseTask.Wait(); // Wait for the task to complete
    
        return responseTask.Result;
    }
    

    【讨论】:

      猜你喜欢
      • 2020-10-04
      • 2013-03-22
      • 1970-01-01
      • 2020-01-02
      • 2020-03-13
      • 1970-01-01
      • 2019-05-31
      • 2019-06-16
      相关资源
      最近更新 更多