C# 如何取消一个进度条?

C# How to cancel a progress bar?

我正在尝试使用取消按钮从这个 Progress bar tutorial 改进代码,但到目前为止没有成功:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private CancellationTokenSource cts;

    private void Calculate(int i)
    {
        Math.Pow(i, i);
    }

    public void DoWork(IProgress<int> progress, CancellationToken cancelToken)
    {
        for (int j = 0; j < 100000; j++)
        {
            if (cancelToken.IsCancellationRequested)
                cancelToken.ThrowIfCancellationRequested();
            Calculate(j);
            progress?.Report((1 + j) * 100 / 100000);
        }
    }

    private async void run_Click(object sender, EventArgs e)
    {
        cts = new CancellationTokenSource();
        var cancelToken = cts.Token;
        progressBar.Maximum = 100;
        progressBar.Step = 1;
        var progress = new Progress<int>(v => progressBar.Value = v);

        try
        {
            await Task.Run(() => { DoWork(progress, cancelToken); }, cts.Token);
        }
        catch (OperationCanceledException ex)
        {
            Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {ex.Message}");
        }
        finally
        {
            cts = null;
        }
    }

    private void cancel_Click(object sender, EventArgs e)
    {
        Console.WriteLine("Cancel");
        cts?.Cancel();
    }
}

单击 运行 后,UI 冻结,我无法单击“取消”按钮。我读过那些博客:

但找不到答案,我也试过这个变体:

    await Task.Factory.StartNew(() => { DoWork(progress, cancelToken); }, cts.Token);

而且没用,加载过程中无法点击取消按钮。任何的想法? (我敢肯定这简单得离谱)。

can't click on the cancel button during the loading. Any idea? (I'm sure it's ridiculously simple).

正如其他人所指出的,您的 DoWork 需要做更多的工作才能中断 UI 线程进行另一次更新。所以这样的事情应该有效:

private void Calculate(int i)
{
  for (int j = 0; j != 10000; ++j)
    Math.Pow(i, i);
}

目前,UI 线程被进度更新淹没,因此它没有时间响应用户输入。

如果您的真实世界代码不容易拆分成更大的块,您可以使用速率限制 IProgress<T> 实现,例如 one I wrote when updating my book:

public static class ObservableProgress
{
  public static (IObservable<T> Observable, IProgress<T> Progress) CreateForUi<T>(TimeSpan? sampleInterval = null)
  {
    var (observable, progress) = Create<T>();
    observable = observable.Sample(sampleInterval ?? TimeSpan.FromMilliseconds(100))
        .ObserveOn(SynchronizationContext.Current);
    return (observable, progress);
  }

  public static (IObservable<T> Observable, IProgress<T> Progress) Create<T>()
  {
    var progress = new EventProgress<T>();
    var observable = Observable.FromEvent<T>(handler => progress.OnReport += handler, handler => progress.OnReport -= handler);
    return (observable, progress);
  }

  private sealed class EventProgress<T> : IProgress<T>
  {
    public event Action<T> OnReport;
    void IProgress<T>.Report(T value) => OnReport?.Invoke(value);
  }
}

用法:

private async void run_Click(object sender, EventArgs e)
{
  cts = new CancellationTokenSource();
  var cancelToken = cts.Token;
  progressBar.Maximum = 100;
  progressBar.Step = 1;
  var (observable, progress) = ObservableProgress.CreateForUi<int>();

  try
  {
    using (observable.Subscribe(v => progressBar.Value = v))
      await Task.Run(() => { DoWork(progress, cancelToken); }, cts.Token);
  }
  catch (OperationCanceledException ex)
  {
    Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {ex.Message}");
  }
  finally
  {
    cts = null;
  }
}

速率限制 IProgress<T> 将丢弃额外的进度更新,每 100 毫秒仅向 UI 线程发送一个进度更新,它应该能够轻松处理并保持对用户的响应互动。