Hello, comrades) I've found some interesting behavior of Invalidate method in multithreaded applications. I hope you could help me with a problem...
I experience troubles while trying to invalidate different controls at one time: while they're identical, one succesfully repaints itself, but another - not.
Here is an example: I have a form (MysticForm) with two panels (SlowRenderPanel) on it. Each panel has a timer and with a period of 50ms Invalidate() method is called. In OnPaint method I draw number of current OnPaint call in the centre of panel. But notice that in OnPaint method System.Threading.Thread.Sleep(50) is called to simulate long time draw procedure.
So the problem is that the panel added first repaints itself much more often than another one.
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);
}
}
}
Debug info:
1) Each panel has a bool field useUnsafeTime (set to true by default) which allows using System.Windows.Forms.Timer (false) insted of System.Threading.Timer (true). In the first case (System.Windows.Forms.Timer) everything works fine. Removing System.Threading.Sleep call in OnPaint also makes execution fine.
2) Setting timer interval to 25ms or less prevents second panel repainting at all (while user doesn't resize the form).
3) Using System.Windows.Forms.Timer leads to speed increasement
4) Forcing control to enter synchronization context (Invoke) doesn't make sense. I mean that Invalidate(invalidateChildren = false) is "thread-safe" and could possibly have different behavior in diffenent contexts
5) Nothing interesting found in IL comparison of these two timers... They just use different WinAPI functions to set and remove timers (AddTimerNative, DeleteTimerNative for Threading.Timer; SetTimer, KillTimer for Windows.Forms.Timer), and Windows.Forms.Timer uses NativeWindow's WndProc method for rising Tick event
I use a similar code snippet in my application and unfortunately there is no way of using System.Windows.Forms.Timer) I use long-time multithreaded image rendering of two panels and Invalidate method is called after rendering is completed on each panel...
That would be great if someone could help me to understand what's different happening behind the scenes and how to solve the problem.
P.S. Interesting behavior isn't it?=)