Мое консольное приложение преждевременно закрывается при использовании async / await? - PullRequest
0 голосов
/ 01 марта 2019

У меня есть консольное приложение, все, что он делает, это перебирает всех клиентов и отправляет электронные письма определенным, а затем отключается.Я заметил, что некоторые функции в MailKit предлагают асинхронные, поэтому я попытался использовать их вместо non-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;
    }

  }
}

Чего я не понимаю, почему бы моему циклу не продолжать цикл по всем клиентам?- есть больше работы для этого.Не уверен, почему он вернется к основной функции.

Я надеялся, что цикл продолжится через всех клиентов и отправит электронную почту;не нужно ждать отправки письма, прежде чем перейти к следующему.

Я ценю вашу помощь!

Ответы [ 3 ]

0 голосов
/ 01 марта 2019

Похоже, вы не понимаете, как работает await.Каждый раз, когда вы вызываете await для функции, сама функция вызывающей стороны временно возвращается, пока не вернется ожидаемая функция.

Это будет означать, что, если ожидаемая функция не вернется вовремя, ваш основной поток завершит выполнение Main и выйдет из приложения.Чтобы это исправить, вам нужно изменить тип возврата для SendReportsToCustomers() на что-то вроде этого:

public async Task SendReportsToCustomers()

Теперь вы можете подождать, пока он не закончится.Блокирующим способом:

reportServices.SendReportsToCustomers().Result();

или

reportServices.SendReportsToCustomers().Wait();

или в неблокирующем ожидании с await:

await reportServices.SendReportsToCustomers();
0 голосов
/ 01 марта 2019

Во-первых, вы должны ИЗБЕГАТЬ асинхронных пустых методов, ReportsServices.SendReportsToCustomers() должен возвращать Task.Ваш код вызывает метод, но не ожидает его завершения, метод является асинхронным, поэтому он возвращается при первом ожидании. Вы должны дождаться его завершения в Main ().Есть 2 способа сделать это:

Если вы используете 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.
}

Вот статья с дальнейшими пояснениями.

0 голосов
/ 01 марта 2019

Я надеялся, что цикл будет проходить через всех клиентов и отправлять электронные письма;не нужно ждать отправки электронного письма, прежде чем перейти к следующему.

Ваш цикл этого не делает.Это фактически делает одно электронное письмо за один раз.Однако вызывающий поток немедленно получает управление.

Эта строка ниже фактически гласит: «сделайте это в другом потоке, а затем продолжите запускать этот метод после завершения».Между тем, поскольку 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.

Чего я не понимаю, так это почему мой цикл не продолжит цикл по всем клиентам?- есть больше работы для этого.Не уверен, почему он вернется к основной функции.

См. await документацию , чтобы лучше понять ее использование и неблокирующую природу.

Вна очень высоком уровне вы можете думать о await как о том, что «не выполняйте оставшуюся часть этого метода до тех пор, пока эта задача не будет выполнена, но не блокируйте и то и другое».

...