使用async / await时,我的控制台应用程序提前关闭?

问题描述 投票:0回答:3

我有一个控制台应用程序,它所做的只是遍历所有客户并发送电子邮件到特定的,然后关闭。我注意到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函数。

我希望的是,循环将继续通过所有客户并发送电子邮件;在继续下一封电子邮件之前,无需等待电子邮件发送。

我感谢您的帮助!

c# multithreading mailkit
3个回答
1
投票

首先,你应该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进一步解释。


2
投票

看来你不明白await是如何工作的。每次在函数上调用await时,调用函数本身都将暂时返回,直到等待函数返回为止。

这意味着,除非等待的函数及时返回,否则主线程将完成执行Main并退出应用程序。要解决此问题,您需要将SendReportsToCustomers()的返回类型更改为以下内容:

public async Task SendReportsToCustomers()

现在,您可以等待它完成。阻止方式:

reportServices.SendReportsToCustomers().Result();

要么

reportServices.SendReportsToCustomers().Wait();

或者在await的非阻塞等待中:

await reportServices.SendReportsToCustomers();

1
投票

我希望的是,循环将继续通过所有客户并发送电子邮件;在继续下一封电子邮件之前,无需等待电子邮件发送。

你的循环没有这样做。它实际上是一次发送一封电子邮件。但是,调用线程立即获得控制权。

下面这一行有效地读取“在另一个线程上执行此操作,然后在完成后继续运行此方法”。与此同时,由于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视为“在此任务完成之前不执行此方法的其余部分,但不要阻止”。

© www.soinside.com 2019 - 2024. All rights reserved.