由代码触发时,等待任务不起作用,但如果由用户触发,则它起作用

时间:2019-05-12 16:40:12

标签: c# task task-parallel-library

我有用于控制LED灯条的应用程序。 UI具有带效果选择的组合框,当用户选择模式时,它会通过调用StopTask()等待当前正在运行的效果循环完成,然后执行选定的效果。它通过串行将LED颜色等发送到Arduino。这行得通。

问题是当我通过MainWindow_OnClosing触发StopTask()时(当用户退出应用程序时),它将触发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();
    });
}

我是Tasks的初学者,而且我很确定自己没有正确使用它们(其中有些是不必要的,但我不知道),这也许就是它无法正常工作的原因。我尝试搜索并找到了许多使用Tasks的示例,但我仍然不太了解。

谢谢!

1 个答案:

答案 0 :(得分:2)

MainWindow_OnClosing()更改为async void,并使用await Disconnect()而不是调用.Wait()。事件处理程序几乎是唯一可以接受的异步方法。其余的应该使用async Task[<T>]签名。 (异步部分有一些例外,但任务部分没有例外,但在这里我不会为此困惑)。这样可以阻止您进行屏蔽(有关更多信息,请参阅Dmytro评论中的链接)。

在那里,将CbMode_OnSelectionChanged()更改为相似的{async void),将ChangeMode() async Task改为await

唯一需要注意的一点是,如果将关闭设备的代码移到事件处理程序中(或将其重构为事件处理程序在await Disconnect()之后调用的另一个方法),则应该不需要Invoke(),因为异步事件处理程序-正确完成-免费为您提供;即有效地保留在UI线程上而不被阻塞。 (我想这就是您想要达到的目标?)

相关问题