【发布时间】:2020-01-14 16:55:17
【问题描述】:
我正在尝试使用取消按钮从 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();
}
}
点击运行后,用户界面冻结,我无法点击取消按钮。我读过这些博客:
- 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);
而且它不起作用,在加载过程中无法点击取消按钮。任何想法? (我敢肯定这非常简单)。
【问题讨论】:
-
看起来您的每次迭代都在报告进度,并且没有延迟。然后,您的循环将运行得太快,无法让 ui 更新和响应。将
await Task.Delay(TimeSpan)之类的内容添加到您的DoWork -
查看Progress<> 的文档:Progress 类在其创建时捕获同步上下文(此处为 UI 线程),然后切换到此同步。调用
Report时的上下文。因此,对于非常紧凑的循环——比如这里——你会不断地切换到 UI 线程,并考虑到更新进度比你的Compute函数更昂贵,你的DoWork方法主要在 UI 线程上运行并阻塞它。您应该每 1000 件左右报告一次。 -
谢谢你们,我只是在
progress?.Report((1 + j) * 100 / 100000000);之前添加了if (j % 500 == 0)并将循环增加到100000000,它就像一个魅力。正如你所说,代码实际上是正确的,DoWork没有做足够的工作。
标签: c# task progress-bar