我有一个控制台应用程序,它所做的只是遍历所有客户并发送电子邮件到特定的,然后关闭。我注意到MailKit中的一些函数提供了异步,所以我尝试使用它们而不是非aysnc,当我这样做时,它执行了第一个语句(emailClient.ConnectAsync)然后我注意到我的控制台应用程序正在关闭。它没有崩溃。执行返回Main()并在调用SendReports()函数后继续执行。
private static void Main(string[] args)
{
...
var reportServices = new ReportsServices();
reportServices.SendReportsToCustomers();
Log.CloseAndFlush(); // It executes the first await call then returns here.
}
internal class ReportsServices
{
public async void SendReportsToCustomers()
{
try
{
foreach (var customer in _dbContext.Customer)
{
...
await SendReport(customer.Id, repTypeId, freqName);
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
private async Task SendReport(int customerId, int repTypeId, string freqName)
{
try
{
...
var es = new EmailSender();
await es.SendAsync();
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
public class EmailSender
{
public async Task SendAsync()
{
try
{
var message = new MimeMessage();
...
using (var emailClient = new SmtpClient())
{
await emailClient.ConnectAsync("smtp.gmail.net", 587);
await emailClient.AuthenticateAsync("username", "password"); // If I put a debug break here, it doesn't hit.
await emailClient.SendAsync(message);
await emailClient.DisconnectAsync(true);
// If I use the following calls instead, my console app will not shutdown until all the customers are sent emails.
await emailClient.Connect("smtp.gmail.net", 587);
await emailClient.Authenticate("username", "password"); // If I put a debug break here, it doesn't hit.
await emailClient.Send(message);
await emailClient.Disconnect(true);
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
我没有得到的是为什么我的循环不会继续遍历所有客户? - 还有更多的工作要做。不知道为什么它会跳回到Main函数。
我希望的是,循环将继续通过所有客户并发送电子邮件;在继续下一封电子邮件之前,无需等待电子邮件发送。
我感谢您的帮助!
首先,你应该AVOID async void方法,ReportsServices.SendReportsToCustomers()
应该返回Task
。您的代码调用该方法但不等待它完成,该方法是异步的,因此它在第一个await返回。您应该等待它在Main()中完成。有两种方法可以做到:
如果您使用C#7,则允许异步Main:
private static async Task Main(string[] args)
{
...
var reportServices = new ReportsServices();
await reportServices.SendReportsToCustomers();
Log.CloseAndFlush(); // It executes the first await call then returns here.
}
如果没有,您将必须同步等待操作完成:
private static void Main(string[] args)
{
...
var reportServices = new ReportsServices();
reportServices.SendReportsToCustomers().Wait();;
Log.CloseAndFlush(); // It executes the first await call then returns here.
}
这是一个article进一步解释。
看来你不明白await
是如何工作的。每次在函数上调用await
时,调用函数本身都将暂时返回,直到等待函数返回为止。
这意味着,除非等待的函数及时返回,否则主线程将完成执行Main
并退出应用程序。要解决此问题,您需要将SendReportsToCustomers()
的返回类型更改为以下内容:
public async Task SendReportsToCustomers()
现在,您可以等待它完成。阻止方式:
reportServices.SendReportsToCustomers().Result();
要么
reportServices.SendReportsToCustomers().Wait();
或者在await
的非阻塞等待中:
await reportServices.SendReportsToCustomers();
我希望的是,循环将继续通过所有客户并发送电子邮件;在继续下一封电子邮件之前,无需等待电子邮件发送。
你的循环没有这样做。它实际上是一次发送一封电子邮件。但是,调用线程立即获得控制权。
下面这一行有效地读取“在另一个线程上执行此操作,然后在完成后继续运行此方法”。与此同时,由于await
没有阻塞,调用线程继续执行。
await SendReport(customer.Id, repTypeId, freqName);
我会建议这样做,它会启动你的所有报告,并为完成所有报告做一个await
:
await Task.WhenAll(_dbContext.Customer.Select(x => SendReport(x.Id, repTypeId, freqName));
我强烈建议在使用Task
时总是返回async
:
public async Task SendReportsToCustomers()
通过这种方式,调用者可以使用await
或使用返回值执行任何操作。你甚至可以用Task.Result
阻止它。
我没有得到的是为什么我的循环不会继续遍历所有客户? - 还有更多的工作要做。不知道为什么它会跳回到Main函数。
请参阅await
documentation以更好地了解它的用法和非阻塞性质。
在非常高的层次上,您可以将await
视为“在此任务完成之前不执行此方法的其余部分,但不要阻止”。