Sab*_*ncu 0 .net c# multithreading invoke winforms
我试图理解Windows Forms中的某个长期概念:UI编程; 以下代码来自Chris Sells的Windows窗体编程书(第2版,2006):
void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
// Display progress in UI
this.resultsTextBox.Text = pi;
this.calcToolStripProgressBar.Maximum = totalDigits;
this.calcToolStripProgressBar.Value = digitsSoFar;
if( digitsSoFar == totalDigits ) {
// Reset UI
this.calcToolStripStatusLabel.Text = "Ready";
this.calcToolStripProgressBar.Visible = false;
}
// Force UI update to reflect calculation progress
this.Refresh();
}
Run Code Online (Sandbox Code Playgroud)
此方法是小样本应用程序的一部分,它具有另一个计算Pi的长期运行方法.每次计算一组数字时,都会调用ShowProgress()来更新UI.正如书中所解释的,这段代码是"错误"的处理方式,并且当应用程序最小化时会导致UI冻结,然后再次进入前台,导致系统要求应用程序重新绘制自己.
我不明白:由于this.Refresh()被重复调用,为什么它不处理任何等待注意的系统重绘事件?
还有一个后续问题:当我在this.Refresh()之后立即添加Application.DoEvents()时,冻结问题就会消失. 这是不必诉诸于Invoke/BeginInvoke等.任何评论?
基本上,原因是Windows处理消息的方式 - 它在内部消息循环中以同步方式执行此操作.
关键是有一条消息触发了您的代码.例如按一下按钮.您的应用程序正在处理消息.在此处理程序中,您强制刷新,将另一个WM_PAINT放入消息队列中.当你的处理程序完成时,消息循环肯定会拾取并调度,从而重新绘制控件.但是你的代码还没有完成,实际上它会循环调用你的代码ShowProgress,导致WM_PAINT永远排队.
另一方面,DoEvents()导致消息循环的独立实例触发.它从发射中的代码,这意味着调用堆栈看起来是这样的:
外部消息循环 - >您的代码 - >内部消息循环.
内部消息循环处理所有未决消息,包括WM_PAINT(因此控件被重新绘制),但它很危险 - 因为它将调度所有其他待处理消息,包括按钮点击,菜单点击或事件关闭您的应用程序,顶部的X -右上角.遗憾的是,没有简单的方法可以使循环仅处理WM_PAINT,这意味着调用DoEvents()会使您的应用程序暴露于涉及触发DoEvents的代码执行过程中意外用户活动的细微潜在问题.