同志们)我在多线程应用程序中发现了 Invalidate 方法的一些有趣行为。我希望你能帮我解决一个问题...
我在尝试一次使不同的控件无效时遇到了麻烦:虽然它们是相同的,但一个成功地重新绘制了自己,但另一个 - 没有。
这是一个示例:我有一个表单(MysticForm),上面有两个面板(SlowRenderPanel)。每个面板都有一个计时器,并以 50 毫秒为周期调用 Invalidate() 方法。在 OnPaint 方法中,我在面板中心绘制当前 OnPaint 调用的数量。但请注意,在 OnPaint 方法中调用 System.Threading.Thread.Sleep(50) 来模拟长时间绘制过程。
所以问题是首先添加的面板比另一个面板更频繁地重新绘制自己。
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1 {
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MysticForm());
}
}
public class MysticForm : Form {
public SlowRenderPanel panel1;
public SlowRenderPanel panel2;
public MysticForm() {
// add 2 panels to the form
Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Left, BackColor = Color.Red, Width = ClientRectangle.Width / 2 });
Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Right, BackColor = Color.Blue, Width = ClientRectangle.Width / 2 });
}
}
public class SlowRenderPanel : Panel {
// synchronized timer
private System.Windows.Forms.Timer timerSafe = null;
// simple timer
private System.Threading.Timer timerUnsafe = null;
// OnPaint call counter
private int counter = 0;
// allows to use one of the above timers
bool useUnsafeTimer = true;
protected override void Dispose(bool disposing) {
// active timer disposal
(useUnsafeTimer ? timerUnsafe as IDisposable : timerSafe as IDisposable).Dispose();
base.Dispose(disposing);
}
public SlowRenderPanel() {
// anti-blink
DoubleBuffered = true;
// large font
Font = new Font(Font.FontFamily, 36);
if (useUnsafeTimer) {
// simple timer. starts in a second. calls Invalidate() with period = 50ms
timerUnsafe = new System.Threading.Timer(state => { Invalidate(); }, null, 1000, 50);
} else {
// safe timer. calls Invalidate() with period = 50ms
timerSafe = new System.Windows.Forms.Timer() { Interval = 50, Enabled = true };
timerSafe.Tick += (sender, e) => { Invalidate(); };
}
}
protected override void OnPaint(PaintEventArgs e) {
string text = counter++.ToString();
// simulate large bitmap drawing
System.Threading.Thread.Sleep(50);
SizeF size = e.Graphics.MeasureString(text, Font);
e.Graphics.DrawString(text, Font, Brushes.Black, new PointF(Width / 2f - size.Width / 2f, Height / 2f - size.Height / 2f));
base.OnPaint(e);
}
}
}
调试信息:
1) 每个面板都有一个 bool 字段 useUnsafeTime(默认设置为 true),它允许使用 System.Windows.Forms.Timer (false) insted of System.Threading.Timer (true)。在第一种情况下(System.Windows.Forms.Timer)一切正常。删除 OnPaint 中的 System.Threading.Sleep 调用也可以使执行正常。
2) 将计时器间隔设置为 25 毫秒或更短,完全可以防止第二个面板重绘(而用户不调整表单大小)。
3)使用 System.Windows.Forms.Timer 导致速度增加
4)强制控制进入同步上下文(调用)没有意义。我的意思是 Invalidate(invalidateChildren = false) 是“线程安全的”,并且在不同的上下文中可能有不同的行为
5) 在这两个定时器的 IL 比较中没有发现什么有趣的东西……它们只是使用不同的 WinAPI 函数来设置和删除定时器(Threading.Timer 的 AddTimerNative、DeleteTimerNative;Windows.Forms.Timer 的 SetTimer、KillTimer)和 Windows.Forms .Timer 使用 NativeWindow 的 WndProc 方法来提升 Tick 事件
我在我的应用程序中使用了类似的代码片段,不幸的是无法使用 System.Windows.Forms.Timer)我使用两个面板的长时间多线程图像渲染,并且在每个面板上完成渲染后调用 Invalidate 方法.. .
如果有人可以帮助我了解幕后发生的不同以及如何解决问题,那就太好了。
PS有趣的行为不是吗?=)