【问题标题】:Cause of TaskCanceledException with SendMailAsync?使用 SendMailAsync 导致 TaskCanceledException 的原因?
【发布时间】:2019-03-28 20:44:27
【问题描述】:

我不明白这两种调用 SendMailAsync 的实现之间的区别。在第一次的大部分时间里,我都收到了一个 TaskCanceledException,但在第二次中,一切都按预期工作。

我认为这两种消费方法是等效的,但显然我错过了一些东西。

这似乎与 TaskCanceledException when not awaiting 相关但相反

// Common SmtpEmailGateway library
public class SmtpEmailGateway : IEmailGateway
{
    public Task SendEmailAsync(MailMessage mailMessage)
    {
        using (var smtpClient = new SmtpClient())
        {
            return smtpClient.SendMailAsync(mailMessage);
        }
    }
}

// Caller #1 code - Often throws TaskCanceledException
public async Task Caller1()
{
     // setup code here
     var smtpEmailGateway = new SmtpEmailGateway();
     await smtpEmailGateway.SendEmailAsync(myMailMessage);
}

// Caller #2 code - No problems
public Task Caller2()
{
     // setup code here
     var smtpEmailGateway = new SmtpEmailGateway();
     return smtpEmailGateway.SendEmailAsync(myMailMessage);
}

编辑:原来 Caller2 方法也导致了异常,我只是没有看到它们,因为它被调用的 WebJobs 框架。 Yuval 的解释澄清了这一切,而且是正确的。

【问题讨论】:

  • 你怎么打电话给Caller1Caller2
  • Caller1 和 Caller2 是 Azure WebJobs SDK 调用的公共方法。 SDK 支持异步方法。

标签: c# async-await smtpclient


【解决方案1】:

您的两个方法调用之间的区别在于前者在被调用时使用await 异步等待。因此,TaskCanceledException 从内部的SendEmailAsync 调用传播,这是由于您没有等待using 范围内的异步方法,这会导致SmtpClient 的处置和异步调用的结束。而在后者中,异常被封装在 return Task 对象中,我不确定您是否正在等待。这就是为什么在前者中,您会立即看到异常。

应该做的第一件事是在网关内正确等待SendEmailAsync

public class SmtpEmailGateway : IEmailGateway
{
    public async Task SendEmailAsync(MailMessage mailMessage)
    {
        using (var smtpClient = new SmtpClient())
        {
            return await smtpClient.SendMailAsync(mailMessage);
        }
    }
}

然后,您可以使用第二种方法来避免创建状态机的开销。不同之处在于您现在保证 SmtpClient 只会在异步操作完成后释放。

【讨论】:

  • 如果我有这个正确的:我不应该从一个对象返回一个任务而不等待它在该对象的 using 语句中。这是因为该对象可能会在任务完成之前被处理掉。对吗?
  • 我的 Caller2 方法似乎也导致了我没有捕捉到的异常,但 WebJobs 框架正在捕捉并重试。
  • @BrianVallelunga 没错。这就是我试图传达的。它被封装在Task 中,但任何等待它的人都会传播异常。
【解决方案2】:

我把这个答案放在这里,不是因为它与这个问题的细节有关,而是与问题有关(另外,这是我的第二个是时候找到这个问题了)。

对我来说,我只是从 using 中返回任务。

public Task SendEmailAsync( EmailInfo email, string smtpServer, int smtpPort )
{
    using( var client = new SmtpClient( smtpServer, smtpPort ) )
    using( var msg = CreateMailMessageFromEmailInfo( email ) )
        return client.SendMailAsync( msg );
}

所以我怀疑这里发生的事情是在返回任务之前使用已经完成,从而取消了任务。我将代码更改为async,并在等待client.SendMailAsync,它成功了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-05
    • 2018-08-04
    • 1970-01-01
    • 2019-03-17
    • 2012-06-19
    • 2023-03-27
    相关资源
    最近更新 更多