使用异步/等待时我的控制台应用程序过早关闭了吗?

时间:2019-02-28 22:24:43

标签: c# multithreading mailkit

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

我希望的是,这种循环将持续遍及所有客户并发送电子邮件;无需等待发送电子邮件,就可以继续发送下一封电子邮件。

感谢您的帮助!

4 个答案:

答案 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() 以防止您的控制台应用程序突然结束。