Разрешить пользователю немедленно отменить длительную операцию WPF с помощью кнопки - PullRequest
0 голосов
/ 24 января 2019

В приложении 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"));
}

Обратите внимание, что в вышеприведенном обработчике команд мы используем Messenger из набора инструментов MVVM Light Toolkit для указания кода для запуска ротатора.

Теперь в 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.Как мне сделать так, чтобы это немедленно прекратилось в этом сценарии?

Я смотрел на этот пример , но он предполагает, что задача и действия находятся в одном классе;здесь у меня есть модель представления и код-позади.Спасибо.

1 Ответ

0 голосов
/ 24 января 2019

Вы не можете (на практике) и не должны.

Если вы хотите прекратить работу в другом потоке, то правильным способом является сигнализация потока (как вы сделали) ипозвольте потоку остановиться самостоятельно.Так как ваш пример рабочей нагрузки - Thread.Sleep(5000), после достижения этого потока этот поток больше не может ничего делать, пока не истечет время ожидания.Другими словами, вы можете правильно сигнализировать поток и, если он спит, он будет жить до завершения сна, а затем снова проверит сигнал.

Параметры:

  • В этом случае вы можете передать Token моделируемой рабочей нагрузке, используя Task.Wait(5000, token) и используя это вместо Thread.Sleep(5000).Таким образом, смоделированная работа также может быть отменена.
  • Для реальной работы;Вы должны использовать свое лучшее суждение, чтобы проверить сигнал, когда и где он выглядит справедливым, чтобы вы не ожидали возвращения в течение длительного периода времени, когда сигнал отмены сигнализируетПросто обратите внимание, что заставить вызывающего ждать, пока Thread или Task правильно завершится, - это лучший способ сделать это.Вот почему Thread.Abort получает столько критики и обескураживается.
  • Другой вариант - выстрелить и забыть.Вы можете позвонить Cancel() на источнике токена и затем двигаться дальше, не дожидаясь завершения Task или Thread.Вы должны проектировать с учетом этого, но это практично.
...