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 冻结,我无法单击“取消”按钮。我读过那些博客:
- https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
- Accessing UI controls in Task.Run with async/await on WinForms
- https://devblogs.microsoft.com/csharpfaq/parallel-programming-task-cancellation/
- https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/fine-tuning-your-async-application
但找不到答案,我也试过这个变体:
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 线程发送一个进度更新,它应该能够轻松处理并保持对用户的响应互动。
我正在尝试使用取消按钮从这个 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 冻结,我无法单击“取消”按钮。我读过那些博客:
- https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
- Accessing UI controls in Task.Run with async/await on WinForms
- https://devblogs.microsoft.com/csharpfaq/parallel-programming-task-cancellation/
- https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/fine-tuning-your-async-application
但找不到答案,我也试过这个变体:
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 线程发送一个进度更新,它应该能够轻松处理并保持对用户的响应互动。