首页 > 解决方案 > 等待任务在由代码触发时不起作用,但如果由用户触发,它可以工作

问题描述

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

问题是当我通过 MainWindow_OnClosing 触发 StopTask() 时(当用户退出应用程序时),它触发 StopTask() 但卡在 await 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的初学者,我很确定我没有正确使用它们(其中一些肯定是不必要的,但我不知道),也许这就是它不起作用的原因。我尝试搜索并找到了许多使用任务的示例,但我仍然不太了解。

谢谢!

标签: c#tasktask-parallel-library

解决方案


改为MainWindow_OnClosing()be async void,并使用await Disconnect()而不是调用.Wait(). 事件处理程序几乎是唯一可接受的异步方法;其余的应该有一个async Task[<T>]签名。(异步部分有一些例外,但任务部分除外,但我不会在这里混淆水域)。这将阻止您阻止(有关更多信息,请参见 Dmytro 评论中的链接)。

在那里,更改CbMode_OnSelectionChanged()为相似 ( async void)、makeChangeMode() async Taskawaitit。

唯一需要注意的另一件事是,如果您将设备关闭代码移动到您的事件处理程序中(或将其重构为您从事件处理程序调用的另一个方法,之后await Disconnect()),您不应该需要Invoke(), 作为异步事件处理程序- 正确完成 - 免费为您提供此服务;即在不阻塞的情况下有效地保留在 UI 线程上。(我假设这就是你想要在那里实现的目标?)


推荐阅读