问题
It seems like calling Invoke on a winforms control in a callback from a System.Threading.Timer leaks handles until the timer is disposed. Does anyone have an idea of how to work around this? I need to poll for a value every second and update the UI accordingly.
I tried it in a test project to make sure that that was indeed the cause of the leak, which is simply the following:
System.Threading.Timer timer;
public Form1()
{
InitializeComponent();
timer = new System.Threading.Timer(new System.Threading.TimerCallback(DoStuff), null, 0, 500);
}
void DoStuff(object o)
{
this.Invoke(new Action(() => this.Text = "hello world"));
}
This will leak 2 handles/second if you watch in the windows task manager.
回答1:
Invoke acts like a BeginInvoke/EndInvoke pair in that it posts the message to the UI thread, creates a handle, and waits on that handle to determine when the Invoked method is complete. It is this handle that is "leaking." You can see that these are unnamed events using Process Explorer to monitor the handles while the application is running.
If IASyncResult was IDisposable, disposal of the object would take care of cleaning up the handle. As it is not, the handles get cleaned up when the garbage collector runs and calls the finalizer of the IASyncResult object. You can see this by adding a GC.Collect() after every 20 calls to DoStuff -- the handle count drops every 20 seconds. Of course, "solving" the problem by adding calls to GC.Collect() is the wrong way to address the issue; let the garbage collector do its own job.
If you do not need the Invoke call to be synchronous, use BeginInvoke instead of Invoke and do not call EndInvoke; the end-result will do the same thing but no handles will be created or "leaked."
回答2:
Is there some reason you cannot use a System.Windows.Forms.Timer here? If the timer is bound to that form you won't even need to invoke.
回答3:
Okay, I gave it a little more time and it looks like it's actually not leaking handles, it's just the indeterminate nature of the garbage collector. I bumped it way up to 10ms per tick and it'd climb up really fast and 30 seconds later would drop back down.
TO confirm the theory I manually called GC.Collect() on each callback (Don't do this in real projects, this was just to test, there's numerous articles out there about why it's a bad idea) and the handle count was stable.
回答4:
Interesting - This is not an answer but based on Andrei's comment I would have thought this would not leak handles the same way however it does leak handles at the same rate the OP mentioned.
System.Threading.Timer timer;
public Form2()
{
InitializeComponent();
}
private void UpdateFormTextCallback()
{
this.Text = "Hello World!";
}
private Action UpdateFormText;
private void DoStuff(object value)
{
this.Invoke(UpdateFormText);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
timer = new System.Threading.Timer(new TimerCallback(DoStuff), null, 0, 500);
UpdateFormText = new Action(UpdateFormTextCallback);
}
来源:https://stackoverflow.com/questions/1603123/how-to-avoid-leaking-handles-when-invoking-in-ui-from-system-threading-timer