Ожидание задачи не работает при запуске по коду, но если по пользователю, это работает - PullRequest
0 голосов
/ 12 мая 2019

У меня есть приложение для управления светодиодной лентой. Пользовательский интерфейс имеет выпадающий список с выбором эффекта, и когда пользователь выбирает режим, он ожидает завершения текущего цикла эффектов, вызывая StopTask (), а затем выполняет выбранный эффект. Он посылает цвет светодиодов и т. Д. В Arduino через последовательный порт. Это работает.

Проблема в том, что когда я запускаю StopTask () с помощью MainWindow_OnClosing (когда пользователь выходит из приложения), он вызывает StopTask (), но застревает в ожидании currentEffectMode. Я попытаюсь объяснить это больше комментариями внутри кода

Выбор режима MainWindow:

private void CbMode_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    // Checkbox selection triggers this
    _ledStrip.LightModes.ChangeMode(CbMode.SelectedIndex);
}

private void MainWindow_OnClosing(object sender, CancelEventArgs e)
{
    // Trigger disconnect and wait for success - this doesn't work (explained below in code comments)
    _ledStrip.LightModes.Disconnect().Wait();
}

Класс световых режимов:

private Task _modeTask;
private CancellationTokenSource _cancellationToken = new CancellationTokenSource();
// This is being triggered by change mode
internal async void ChangeMode(int mode)
{
    // It waits for current loop to finish
    await StopTask();
    switch (mode)
    {
        case (int)Modes.Static:
            // Then assigns new one
            _modeTask = Static(_cancellationToken.Token);
            break;
        case (int)Modes.Breath:
            _modeTask = Breath(_cancellationToken.Token);
            break;
    }
}

internal async Task StopTask()
{
    if (_modeTask == null)
        return;

    // Set cancellation token to cancel
    _cancellationToken.Cancel();
    try
    {
        // and wait for task to finish. This works if triggered by user interaction BUT this is where it gets stuck when called by Disconnect() method (below). It awaits here forever
        await _modeTask;
    }
    catch (TaskCanceledException ex)
    {
        Console.WriteLine(ex.Message);
    }
    catch (OperationCanceledException ex)
    {
        Console.WriteLine(ex.Message);
    }
    finally
    {
        // After sucessful await create new cts
        _cancellationToken.Dispose();
        _cancellationToken = new CancellationTokenSource();
    }
}

// Example of LED effect loop
internal async Task Static(CancellationToken cancellationToken)
{
    while (true)
    {
        cancellationToken.ThrowIfCancellationRequested();

        _ledStrip.FillLedsWithColor();

        // Wait for strip to light up    
        await LightLeds();
        // Delay before next loop round
        await Task.Delay(15, cancellationToken);
    }
}

// This is being called by window onclosing
internal async Task Disconnect()
{
    //Stop current task and close serial connection. _device is serial
    await StopTask();
    Application.Current.Dispatcher.Invoke(() =>
    {
        if (_device.IsOpen())
        {
            _device.Clear();
            _device.Close();
        }
    });
}

// Method for sending LED information to Arduino
internal async Task LightLeds()
{
    if (!_device.IsOpen())
        return;

    await Task.Run(() => 
    {
        for (int i = 0; i < StaticValues.NumLeds; i++)
        {
            _device.Send((byte)i, _ledStrip.Leds[i].LedColor.R, _ledStrip.Leds[i].LedColor.G, _ledStrip.Leds[i].LedColor.B);
        }
        _device.LightUp();
    });
}

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

Спасибо!

1 Ответ

2 голосов
/ 12 мая 2019

Измените MainWindow_OnClosing() на async void и используйте await Disconnect() вместо вызова .Wait(). Обработчики событий являются почти единственными асинхронными методами, для которых это приемлемо; остальные должны иметь подпись async Task[<T>]. (Есть некоторые исключения в асинхронной части, но не в части «Задача», но я не стану мутить воду здесь). Это прекратит блокировку (см. Ссылку в комментарии Дмитрия).

Пока есть, измените CbMode_OnSelectionChanged() на аналогичные (async void), вместо этого сделайте ChangeMode() async Task и await.

Единственное, что следует отметить, это то, что если вы вместо этого переместите код закрытия устройства в свой обработчик событий (или преобразуете его в другой метод, который вы вызываете из обработчика событий после await Disconnect()), вам не нужно Invoke() как асинхронные обработчики событий - все сделано правильно - дают вам это бесплатно; то есть эффективно остается в потоке пользовательского интерфейса, не блокируясь. (Я предполагаю, что это то, что вы пытаетесь достичь там?)

...