问题
In my current project there is a Form class which looks like this:
public partial class FormMain : Form
{
System.Timers.Timer timer;
Point previousLocation;
double distance;
public FormMain()
{
InitializeComponent();
distance = 0;
timer = new System.Timers.Timer(50);
timer.AutoReset = true;
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
timer.Start();
}
private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (previousLocation != null)
{
// some code
UpdateDistanceLabel(distance);
UpdateSpeedLabel(v);
}
previousLocation = Cursor.Position;
}
private void UpdateDistanceLabel(double newDistance)
{
if (!lblDistance.IsDisposed && !IsDisposed)
{
Invoke(new Action(() => lblDistance.Text = String.Format("Distance: {0} pixels", newDistance)));
}
}
private void UpdateSpeedLabel(double newSpeed)
{
if (!lblSpeed.IsDisposed && !IsDisposed)
{
Invoke(new Action(() => lblSpeed.Text = String.Format("Speed: {0} pixels per second", newSpeed)));
}
}
}
As you can see, I am using a System.Timers.Timer object. I know I could use System.Windows.Forms.Timer, but I'm fairly interested in the reason why I'm still getting the exception shown in the title. It gets thrown at the Invoke call in the UpdateDistanceLabel method. What confuses me is that it says "Cannot access disposed object: FormMain" even though I am checking whether it is disposed or not. So that shouldn't happen. I have also tried disposing the timer object in the FormClosing event as well as overriding Dispose(bool) and disposing it there, both of which unfortunately didn't help at all. Also, the exception does not always get thrown, supposedly only when the timer happens to fire whilst the program is exiting. It still happens a lot.
I've seen that there are tons of threads about this, but I've already tried the solutions posted there, most of them involve checking the IsDisposed property - which doesn't work for me. So I guess I am doing something wrong.
So my question: Why does the code posted above fire an exception even though I am checking whether the objects I am accessing are disposed or not?
回答1:
There are two workarounds: either swallow the exception and curse Microsoft for not having included a TryInvoke
and TryBeginInvoke
methods, or else use locking to ensure that no attempt is made to Dispose
the object while it's in use, and no attempt is made to use the object while Dispose
is in progress. I think swallowing the exception is probably better, but some people have a visceral reaction against such things, and using locking it is possible to avoid having the exception occur in the first place.
回答2:
One problem is that you are doing the check on the timer thread, before calling Invoke
. There is a possible race condition, where the Form can be disposed after your check and before the invoked action is executed.
You should be doing the check inside the method (lambda expression in your case) called by Invoke
.
Another possible problem is that you're accessing Cursor.Position
on the timer thread. I'm not sure if this is valid - I'd do this on the main thread. Your code also includes the comment //some code
- so you've presumably omitted some code that you also need to check.
Overall, you'd probably be better using a System.Windows.Forms.Timer
.
回答3:
Here is my solution to your exception if you are interested:
private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
timer.Stop();
Application.DoEvents();
}
.Stop() without .DoEvents() is not enough, as it'll dispose objects without waiting for your thread to finish its work.
回答4:
Create two booleans called 'StopTimer' and 'TimerStopped' with their initial states set to false. Set the timer's AutoReset property to false. Then format the Elapsed method to the following:
Invoke((MethodInvoker)delegate {
// Work to do here.
});
if (!StopTimer)
timer.Start();
else
TimerStopped = true;
This way you are preventing a race condition, checking if the timer should continue and reporting when the method has reached its end.
Now set your FormClosing method to this:
if (!TimerStopped)
{
StopTimer = true;
Thread waiter = new Thread(new ThreadStart(delegate {
while (!TimerStopped) { }
Invoke((MethodInvoker)delegate { Close(); });
}));
waiter.Start();
e.Cancel = true;
}
else
timer.Dispose();
If the timer hasn't stopped yet, a thread is launched to wait until it has done so and then try to close the form again.
来源:https://stackoverflow.com/questions/9669129/problems-solving-cannot-access-disposed-object-exception