我想创建一个有进度条和取消按钮的表单。我的程序中的代码将运行一个算法并更新进度条。我实现了一个原型(下面)。我想知道这是否是一个正确的实现,或者是否有更好的方法来做到这一点。特别是,我想知道跨线程访问UI进度条是否有任何问题。我过去见过代码可以(看起来)正确运行,但在调试器下运行时会引发跨线程异常。
下面是实现可取消进度条的基本表单。
public partial class CancellableProgressForm : Form
{
public CancellableProgressForm()
{
InitializeComponent();
}
public int PercentComplete
{
set
{
progressBar.Value = value;
}
}
private void buttonCancel_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
this.Close();
}
}
字符串
以及在带有运行按钮的表单上测试它的原型代码:
private void buttonRun_Click(object sender, EventArgs e)
{
var progressForm = new CancellableProgressForm();
var progress = new Progress<int>(percent =>
{
progressForm.PercentComplete = percent;
});
Task.Run(() =>
{
DoWork(progress);
progressForm.DialogResult = DialogResult.OK;
progressForm.Close();
});
DialogResult dr = progressForm.ShowDialog();
MessageBox.Show($"Result={dr}");
}
// Prototype code to "do some work" and update the progress bar on the progress form
private void DoWork(IProgress<int> progress)
{
for (int i = 1; i <= 100; ++i)
{
Thread.Sleep(100);
progress.Report(i);
}
}
型
就像我说的,不管有没有调试器,代码都运行得很好。那么为什么不抛出跨线程异常呢?有没有更好的方法来做到这一点呢?
2条答案
按热度按时间6yoyoihd1#
规则是你只能从创建它的线程与UI元素交互。UI组件是线程仿射的。它们不仅不是线程安全的,甚至不是自由线程的。所以要非常小心你在
Task.Run
委托中所做的事情。使用Progress<T>
是可以的,因为这个组件在创建的时候捕获了同步上下文,并通过捕获的同步上下文传播所有报告,从而有效地实施上述线程亲和性规则。你需要做的是将事件处理程序转换为
async void
,然后将await
转换为Task.Run
。在转换await
之后,你就回到了UI线程上。字符串
上面的代码只是朝着正确的方向迈出了一步,而不是一个完整的解决方案。
Progress<T>
类异步地报告进度,这是高效的,因为它不会减慢后台工作,但它可能会产生一些奇怪的排序工件。如果你遇到问题,你可以尝试我在这里发布的同步IProgress<T>
实现。至于如何实现取消功能,您应该通过添加
CancellationToken
参数来使DoWork
可取消。详细信息请参阅本文:Cancellation in Managed Threads。另一篇可能更有用的文章:Async in 4.5: Enabling Progress and Cancellation in Async APIs。
baubqpgj2#
你不能运行一个任务,应该更新一个表单的方式,你现在这样做。
即使希望在此期间不会出现任何问题,您也希望显示进度的Form准备好处理IProgress委托生成的更新。
你需要处理例外情况。
顺便说一句,我已经测试了你的代码,正如预期的那样,当你试图访问Form类,设置它的DialogResult时,它确实会抛出。
您拥有的属性是通过Progress方法访问的。
我的建议是:从
CancellableProgressForm
运行DoWork()
方法。现在你完全控制了那里发生的一切
我添加了一个
CancellationTokenSource
,可以取消进度。DoWork()
方法现在接受一个CancellationToken
,当按下取消按钮时,它报告一个取消请求。在本例中,对话框结果被设置为DialogResult.Abort
。顺便说一句,当你设置一个窗体的
DialogResult
时,这也会关闭窗体。因为它是一个模态对话框,所以你需要处理它。这在
ShowDialog()
返回时完成。字符串
DoWork()
方法可以解耦。它可以是Form类的成员,也可以在其他地方定义,它不与任何Form共享任何东西。型
在调用表单中,您只需显示对话框:
型
剩下的:如果用户使用[X]按钮关闭表单(假设它是可见的).你可以用一行或两行代码解决它。三行代码仍然是可以接受的:)只是开玩笑,但你必须处理它。
目前,如果在任务运行时关闭窗体,则会得到一个
DialogResult.Cancel
。它可能不是您想要的 * 信号 *,因为用户没有明确按下分配给取消操作结果的按钮