我有一个控制台应用程序,它所要做的就是遍历所有客户并将电子邮件发送给特定的客户,然后关闭。我注意到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函数。
我希望的是,这种循环将持续遍及所有客户并发送电子邮件;无需等待发送电子邮件,就可以继续发送下一封电子邮件。
感谢您的帮助!
答案 0 :(得分:2)
您似乎不了解await
的工作方式。每次您在函数上调用await
时,调用函数本身都会暂时返回,直到等待的函数返回为止。
这意味着,除非等待的函数及时返回,否则您的主线程将完成执行Main
并退出应用程序。要解决此问题,您需要将SendReportsToCustomers()
的返回类型更改为以下形式:
public async Task SendReportsToCustomers()
现在,您实际上可以等待它完成。封锁方式:
reportServices.SendReportsToCustomers().Result();
或
reportServices.SendReportsToCustomers().Wait();
或在await
的不阻塞等待中:
await reportServices.SendReportsToCustomers();
答案 1 :(得分:1)
我希望的是,循环将继续贯穿所有 客户并发送电子邮件;不必等待电子邮件发送 在继续下一个之前。
您的循环没有这样做。它实际上一次只发送一封电子邮件。但是,调用线程立即获得控制权。
下面的这一行有效地显示为“在另一个线程上执行此操作,然后在完成后继续运行此方法”。同时,由于keyof
没有阻塞,因此调用线程继续执行。
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
documentation,以更好地了解其用法和非阻塞性。
从高度上讲,您可以想到await
表示“在此任务完成之前不要执行此方法的其余部分,但也不要阻塞”。
答案 2 :(得分:1)
首先,您应该AVOID异步使用void方法,ReportsServices.SendReportsToCustomers()
应该返回Task
。您的代码会调用该方法,但不会等待它完成,因为该方法是异步的,所以它在第一次等待时返回。您应该在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,带有进一步的说明。
答案 3 :(得分:0)
正如其他人所解释的,控制台应用程序不会等待异步方法完成。它将直接跳转到下一行代码。如果它到达 main 函数的末尾,则控制台应用程序将结束。
也许您可以在 Main 函数的末尾添加一个 Console.ReadLine()
以防止您的控制台应用程序突然结束。