SendMailAsync 导致 TaskCanceledException 的原因?
Cause of TaskCanceledException with SendMailAsync?
我不明白调用 SendMailAsync 的这两种实现之间的区别。大多数时候,我收到第一个 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 的解释澄清了所有这些并且是正确的。
您的两个方法调用之间的区别在于,前者在调用时使用 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 中返回任务。
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
,它成功了。
我不明白调用 SendMailAsync 的这两种实现之间的区别。大多数时候,我收到第一个 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 的解释澄清了所有这些并且是正确的。
您的两个方法调用之间的区别在于,前者在调用时使用 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 中返回任务。
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
,它成功了。