C# Асинхронное / ожидание для последовательных задач, связанных с вводом / выводом - PullRequest
0 голосов
/ 24 марта 2020

Я работаю над приложением обновления прошивки и пытаюсь использовать Async / Await, чтобы избежать блокировки пользовательского интерфейса. Проблема, с которой я сталкиваюсь, состоит в том, что процесс обновления прошивки требует последовательности шагов, которые должны выполняться последовательно (что не является асинхронным). Тем не менее, похоже, что asyn c - это еще и путь к go, когда используется пользовательский интерфейс, чтобы избежать зависания приложения. Так как же нам не блокировать пользовательский интерфейс, последовательно завершать длительный ряд задач, а не израсходовать или тратить потоки?

Вот пример, не являющийся асинхронным c, для иллюстрации проблемы:

public class FirmwareUpdateViewModel : ObservableObject
{
   //ctor
   public FirmwareUpdateViewModel()
   {
       // Process should start on page load
       updateFirmware();
   }

   private void updateFirmware()
   {
       //...

       DeviceHandshake();

       SendMessage(Commands.RestartDevice);

       GetFWFileData();

       WaitForDevice(timeout);

       SendMessage(PreliminaryData);

       //...
   }
}

Моя первая попытка была await Task.Run(() => всем:

public class FirmwareUpdateViewModel : ObservableObject
{
   //ctor
   public FirmwareUpdateViewModel()
   {
       updateFirmwareAsync();
   }

   private async void updateFirmwareAsync()
   {
       //...

       await Task.Run(() => DeviceHandshakeAsync());

       await Task.Run(() => SendMessageAsync(Commands.RestartDevice));

       //...
   }
}

Проблема здесь, кроме async void, заключается в том, что из-за вирусной природы Asyn c, когда я жду внутри DeviceHandshakeAsync, плюс, если Task.Run() работает в своем собственном потоке, то мы теряем тонну потоков.

Затем я попытался сбросить Task.Run(() => и просто ждать каждого метода, но, насколько я знаю, под-методы вернутся, когда встретят внутренний await, и начнут запускать следующий под-метод до завершения предыдущего. И теперь я пытаюсь запустить метод firmwareUpdate asyn c, но не ожидаю суб-методов в попытке сохранить их синхронность и бороться с вирусной природой, но это блокирует пользовательский интерфейс:

public class FirmwareUpdateViewModel : ObservableObject
{
   //ctor
   public FirmwareUpdateViewModel()
   {
       Result = new NotifyTaskComplete<bool>(updateFirmwareAsync());
   }

   private async Task<bool> updateFirmwareAsync()
   {
       //...

       DeviceHandshake();

       SendMessage(Commands.RestartDevice);

       //...
   }
}

Проблема здесь, очевидно, в том, что без await s мы работаем синхронно и в потоке пользовательского интерфейса. Ничто еще не "щелкнуло" для меня в понимании правильного подхода здесь. Как мы это делаем правильно?

Редактировать: Я понял, что пропустил большую часть проблемы ... что USB обрабатывается сторонней библиотекой, которая работает синхронно. Поэтому мне нужно как-то обернуть эти функции, чтобы избежать блокировки пользовательского интерфейса:

public class FirmwareUpdateViewModel : ObservableObject
{
   //ctor
   public FirmwareUpdateViewModel()
   {
       // Don't block the UI with this
       updateFirmware();
   }

   private void updateFirmware()
   {
       //...
       // Run all non-blocking but sequentially
       DeviceHandshake();

       SendMessage(Commands.RestartDevice);

       GetFWFileData();

       WaitForDevice(timeout);

       SendMessage(PreliminaryData);

       //...
   }

   private void SendMessage(Command)
   {
      // Existing code, can't make async
      USBLibrary.Write(Command);

   }
}

Вот почему я так растерялся, потому что asyn c хочет распространяться, но я не могу между конструктором и конец библиотеки.

Ответы [ 3 ]

2 голосов
/ 24 марта 2020

Относительно того, сколько потоков потрачено впустую. Этот код:

private async void updateFirmwareAsync()
{
    //...
    await Task.Run(() => DeviceHandshakeAsync());
    await Task.Run(() => SendMessageAsync(Commands.RestartDevice));
    //...
}

... скорее всего тратит практически ноль потоков. Предполагая, что методы DeviceHandshakeAsync и SendMessageAsync являются асинхронными, другими словами, предполагая, что они возвращают Task, поток ThreadPool будет использоваться только для вызова этих методов и получения задач, и затем будет немедленно возвращен в пул потоков, потому что потоку больше нечего делать. Теперь хорошо ведущий себя асинхронный метод при вызове не блокирует вызывающую программу и почти сразу возвращает неполный Task. Таким образом, если эти методы не ведут себя должным образом, поток ThreadPool будет использоваться в течение промежутка времени, измеряемого в микросекундах.

Но что произойдет, если оба метода являются синхронными?

private async void updateFirmwareAsync()
{
    //...
    await Task.Run(() => DeviceHandshake());
    await Task.Run(() => SendMessage(Commands.RestartDevice));
    //...
}

Этот код тратит один поток ThreadPool. Может быть, один и тот же поток или два разных потока, которые используются последовательно. Этот поток уже был создан (если ваш код никогда не использовал ThreadPool до этого момента), поэтому утверждение, что поток потрачен впустую, является завышенным. Точнее было бы сказать, что один поток ThreadPool будет зарезервирован и, таким образом, недоступен для выполнения другой работы на весь период обновления прошивки. Если ваша программа интенсивно использует потоки ThreadPool, что приводит к сбоям, вызванным ThreadPool голоданием , вы можете превентивно увеличить пул потоков, вызвав метод ThreadPool.SetMinThreads во время инициализация приложения.

Btw здесь - мои аргументы о том, почему рекомендуется использовать await Task.Run преимущественно в обработчиках событий приложений пользовательского интерфейса.

0 голосов
/ 24 марта 2020

Вы должны связать функцию ConfigureAwait:

 public async void FirmwareUpdateViewModel() {
     await updateFirmwareAsync().ConfigureAwait(false);
 }

Это позволит асинхронно вызывать задачу, не дожидаясь ее завершения sh, что приводит к остановке рисования пользовательским интерфейсом.

0 голосов
/ 24 марта 2020

Два способа go об этом, я думаю.

1:

private async void updateFirmwareAsync()
{
   await Task.Run(() => 
   { 
       DeviceHandshakeAsync());
       SendMessage(Commands.RestartDevice);
       GetFWFileData();
       WaitForDevice(timeout);
       SendMessage(PreliminaryData);
   });
}

Так что запускайте их последовательно в одной задаче, или

2:

private async void updateFirmwareAsync()
{
   await Task.Run(() => DeviceHandshakeAsync())
             .ContinueWith(() => SendMessage(Commands.RestartDevice))
             .ContinueWith(() => GetFWFileData())
             .ContinueWith(() => WaitForDevice(timeout))
             .ContinueWith(() => SendMessage(PreliminaryData))
   });
}

На практике они достигают почти одинакового, хотя (1) гарантирует, что все они работают в одном и том же потоке, тогда как (2) означает, что они могут выполняться в другом потоке, так что это больше похоже на ваш начальный кратный await идея.

...