使用SendMailAsync的TaskCanceledException的原因?

时间:2015-11-04 14:06:36

标签: c# async-await smtpclient

我不了解这两种调用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的解释清除了所有这些并且是正确的。

2 个答案:

答案 0 :(得分:6)

两个方法调用之间的区别在于前者在被调用时异步等待使用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只有在异步操作完成后才会处置。

答案 1 :(得分:0)

我将这个答案放在这里,不是因为它与该问题的具体有关,而是与所问的问题有关(同样,这是我的第二篇时间找到这个问题)。

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

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,并且可以正常工作。