SendMailAsync引发TaskCanceledException的原因是什么?

7

我不理解调用SendMailAsync的这两种实现方式之间的区别。在第一种方式中,我大部分时间都会收到TaskCanceledException异常,但是在第二种方式中,一切都按预期工作。

我原以为这两种方法是等效的,但显然我漏掉了什么。

这似乎与以下内容有关,但相反:未等待时的TaskCanceledException

// 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 的? - Yuval Itzchakov
Caller1和Caller2是由Azure WebJobs SDK调用的公共方法。而且该SDK支持异步方法。 - Brian Vallelunga
2个回答

6
你的两个方法调用之间的区别在于前者在被调用时使用了await进行异步等待。因此,TaskCanceledException从内部的SendEmailAsync调用中传播出来,这是由于你在using范围内没有等待异步方法导致的竞争条件,这会导致SmtpClient的销毁和异步调用结束之间的冲突。而在后者中,异常被封装在返回的Task对象中,我不确定你是否在等待它。这就是为什么在前者中,你立即看到异常的原因。
首先要做的是在网关内正确地等待SendEmailAsync
public class SmtpEmailGateway : IEmailGateway
{
    public async Task SendEmailAsync(MailMessage mailMessage)
    {
        using (var smtpClient = new SmtpClient())
        {
            return await smtpClient.SendMailAsync(mailMessage);
        }
    }
}

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


如果我理解正确:当在对象的using语句内部时,我不应该返回一个Task而不等待它。这是因为对象很可能会在任务完成之前被处理掉。是这样吗? - Brian Vallelunga
看起来我的Caller2方法也是导致异常的原因,而我却没有捕获它们,但WebJobs框架却捕获并重试了它们。 - Brian Vallelunga
@BrianVallelunga 没错,这就是我想要表达的。它被封装在 Task 中,但任何等待它的人都会使异常传播。 - Yuval Itzchakov

0

我在这里放置这个答案,不是因为它与这个问题的具体细节相关,而是与问题本身相关(此外,这是我第二次发现这个问题)。

对于我来说,我只是从 using 语句中返回了 Task。

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 );
}

所以我怀疑这里发生的情况是,在任务返回之前,using语句已经结束,从而取消了任务。我改变了代码,使其成为async,并等待client.SendMailAsync,然后它就起作用了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接