允许用户从按钮立即取消长时间运行的WPF操作

时间:2019-01-24 20:10:39

标签: c# wpf mvvm task

在WPF应用中,我们有一个按钮,用户可以单击该按钮来触发要加载到VLC Media Player中的视频列表:

<Button Content="{Binding RotatorButtonLabel}" Command="{Binding RotateVideosCommand}" />

在视图模型MainWindowVm中,我们具有用于处理按钮单击的命令:

public ICommand RotateVideosCommand => new RelayCommand(RotateVideos);

private void RotateVideos()
{
    IsRotatorActive = !IsRotatorActive;
    RotatorButtonLabel = IsRotatorActive
        ? "Stop Rotator"
        : "Rotate Videos";
    _rotatorVm = new RotatorVm
    {
        ImageVms = ImagesView.Cast<ImageVm>().ToList(),
        IsRotatorActive = IsRotatorActive
    };

    // This fires off a new thread to run the rotator, otherwise the UI freezes.
    Task.Run(() => Messenger.Default.Send(rotatorVm, "LaunchRotator"));
}

请注意,在上述命令处理程序中,我们使用MVVM Light Toolkit的Messenger来告诉背后的代码启动旋转器。

现在在MainWindow.xaml.cs中,我们有以下代码:

private CancellationTokenSource _cancellationTokenSource = null;
private CancellationToken _cancellationToken;

public MainWindow()
{
    InitializeComponent();
    Messenger.Default.Register<RotatorVm>(this, "LaunchRotator", LaunchRotator);

    // Other logic...
}

然后是以上LaunchRotator所说的:

private void LaunchRotator(RotatorVm rotatorVm)
{
    if (_cancellationToken.IsCancellationRequested)
    {
        _cancellationTokenSource.Dispose();
    }

    if (_cancellationTokenSource == null || _cancellationToken.IsCancellationRequested)
    {
        _cancellationTokenSource = new CancellationTokenSource();
        _cancellationToken = _cancellationTokenSource.Token;
    }

    if (!rotatorVm.IsRotatorActive)
    {
        _cancellationTokenSource.Cancel();
        return;
    }

    RotateVideos(); 
}

private void RotateVideos()
{
    while (true)
    {
        if (_cancellationToken.IsCancellationRequested)
        {
            return;
        }

        // This is to simplify the code and simulate work.
        Thread.Sleep(5000);
    }
}

如果单击“停止旋转器”按钮,则代码可能需要几秒钟才能到达while循环的下一次迭代并读取IsCancellationRequested。在这种情况下,如何使它立即停止?

我看过this example,但这是假设任务和活动都在一个班级中。在这里,我有一个视图模型和一个后台代码。谢谢。

1 个答案:

答案 0 :(得分:2)

您不能(实际上)也不应该。

如果要停止在另一个线程中工作,那么正确的方法是向该线程发出信号(就像您已经完成的那样),并允许该线程自己停止。由于您的示例工作负载是Thread.Sleep(5000),因此一旦被点击,该线程将无法执行任何其他操作,直到睡眠时间到期为止。换句话说,您可以正确发出信号,如果该线程处于睡眠状态,则该线程将一直存活直到睡眠完成,然后它将再次检查该信号。

选项:

  • 在这种情况下,您可以将Token传递给模拟的工作负载 使用Task.Wait(5000, token)并使用它代替 Thread.Sleep(5000)。这样,模拟工作也可以 已取消。
  • 用于实际工作;您必须使用最佳判断来测试信号 何时何地看起来很公平,这样您就不必长时间等待 发出取消信号时返回的时间。请注意,让呼叫者等待 ThreadTask正确地结束自身是最好的方法 它。这就是Thread.Abort受到如此多批评并且是 灰心丧气。
  • 另一个选择是解雇。您可以在令牌源上调用Cancel(),然后继续进行而不必等待TaskThread完成。您必须在设计时考虑到这一点,但这是实用的。