My console app shutdown prematurely when using async / await?

痴心易碎 提交于 2021-02-17 07:04:16

问题


I have a console app that all it does is loop through all the customers and send emails to specific ones and then shutdown. I noticed that some of the functions in MailKit’s offer asynchronous so I tried using them instead of non-aysnc and when I did that, it executed the first statement (emailClient.ConnectAsync) on and then I noticed that my console app was shutting down. It didn’t crash. The execution returned to Main() and continued executing after my call to my SendReports() function.

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;
    }

  }
}

What I don't get is why wouldn't my loop continue looping through all the customers? - there is more work for it. Not sure why it would skip back to the Main function.

What I was hoping for is that loop would continue through all the customers and send emails; not having to wait for the email to be sent before continuing on the next one.

I appreciate your help!


回答1:


First, you should AVOID async void methods, ReportsServices.SendReportsToCustomers() should return Task. Your code invokes the method but does not wait for it to finish, the method is asynchronous so it returns on the first await.you should wait for it to finish in Main(). There are 2 ways to do it:

If you'are using C# 7, async Main is allowed:

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.
}

If not, you'll have to synchronously wait for the operation to finish:

private static void  Main(string[] args)
{
  ...
  var reportServices = new ReportsServices();

  reportServices.SendReportsToCustomers().Wait();;

  Log.CloseAndFlush();  // It executes the first await call then returns here.
}

Here is an article with further explanation.




回答2:


It seems that you do not understand how await works. Each time you call await on a function, the caller function itself will temporarily return, until the awaited function returns.

This will mean that, unless the awaited function returns in time, your main thread will finish executing Main and exit the application. To fix this, you need to change your return type for SendReportsToCustomers() to something like this:

public async Task SendReportsToCustomers()

Now, you can actually wait for it to finish. In a blocking way:

reportServices.SendReportsToCustomers().Result();

or

reportServices.SendReportsToCustomers().Wait();

or in a non blocking wait with await:

await reportServices.SendReportsToCustomers();



回答3:


What I was hoping for is that loop would continue through all the customers and send emails; not having to wait for the email to be sent before continuing on the next one.

Your loop isn't doing this. It's actually doing one email at a time. However, the calling thread is getting control back immediately.

This line below effectively reads "do this on another thread, then continue running this method once that's complete". In the meantime, since await does not block, the calling thread continues execution.

await SendReport(customer.Id, repTypeId, freqName);

I would suggest this instead, which launches all your reports and does an await for completion of all of them:

await Task.WhenAll(_dbContext.Customer.Select(x => SendReport(x.Id, repTypeId, freqName));

I would also highly suggest to always return a Task when using async:

public async Task SendReportsToCustomers()

This way the caller can await or do whatever it wants with the return value. You can even block on it with Task.Result.

What I don't get is why wouldn't my loop continue looping through all the customers? - there is more work for it. Not sure why it would skip back to the Main function.

See await documentation to better understand it's usage and non-blocking nature.

At a very high level, you can think of await as saying "don't execute the rest of this method until this Task completes, but don't block either".



来源:https://stackoverflow.com/questions/54935233/my-console-app-shutdown-prematurely-when-using-async-await

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!