【问题标题】:Allow user to immediately cancel long-running WPF operation from button允许用户从按钮立即取消长时间运行的 WPF 操作
【发布时间】:2019-01-24 20:10:39
【问题描述】:

在 WPF 应用程序中,我们有一个按钮,用户可以单击该按钮来触发要加载到 VLC 媒体播放器中的视频列表:

<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,我们有以下c'tor:

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,但它假设任务和活动都在一个类中;在这里,我有一个视图模型和一个代码隐藏。谢谢。

【问题讨论】:

  • 你能在主要工作中更频繁地测试IsCancellationRequested吗?
  • @redcurry,当你说“主要工作”时,你的意思是在RotateVideos方法中吗?
  • 是的,特别是被Thread.Sleep(5000)替换的作品。
  • 这将启动 VLC,然后Timer(未显示)上的事件处理程序将切换视频。也许启动 VLC 进程不会花那么长时间,所以当用户点击停止时,他们会相对较快地终止操作。

标签: c# wpf mvvm task


【解决方案1】:

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

如果您想在另一个线程中停止工作,那么正确的方法是向线程发出信号(就像您所做的那样)并让线程自行停止。由于您的示例工作负载是Thread.Sleep(5000),因此一旦命中,该线程将无法执行任何其他操作,直到睡眠到期。换句话说,你可以正确地给线程发信号,如果它正在休眠,它会一直存活到休眠完成,然后它会再次检查信号。

选项:

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

【讨论】:

  • 谢谢,@MichaelPuckettII。对于第一个选项,Task.Wait(5000, token) 在我的代码中会放在哪里?
  • @Alex 因为你的方法不是async,你可以用Task.Delay(5000, _cancellationToken).Wait();替换Thread.Sleep(5000);
  • 有趣:.Wait() 有一个可以接受_cancellationToken的重载
  • @Alex 是的,在Wait 中使用它会取消Task 的等待,但Task 本身将继续直到它完成。换句话说,这将属于“一劳永逸”的情况,但我仍然会将令牌传递给Task,以便Task 可以在内部取消。在一个中,您正在向Task 的内部工作发出信号以取消并等待Task 完成,而在另一个中,您正在完全取消等待;在这种情况下,Task 可能仍在运行。
猜你喜欢
  • 1970-01-01
  • 2021-10-29
  • 1970-01-01
  • 2011-11-30
  • 2015-10-08
  • 2012-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多