views:

638

answers:

6

I have the following code in my worker thread (ImageListView below is derived from Control):

if (mImageListView != null && 
    mImageListView.IsHandleCreated &&
    !mImageListView.IsDisposed)
{
    if (mImageListView.InvokeRequired)
        mImageListView.Invoke(
            new RefreshDelegateInternal(mImageListView.RefreshInternal));
    else
        mImageListView.RefreshInternal();
}

However, I get an ObjectDisposedException sometimes with the Invoke method above. It appears that the control can be disposed between the time I check IsDisposed and I call Invoke. How can I avoid that?

A: 

One way might be to call the method itself ones more instead of invoking the ImageListView-Method:

if (mImageListView != null && 
    mImageListView.IsHandleCreated &&
    !mImageListView.IsDisposed)
{
    if (mImageListView.InvokeRequired)
        mImageListView.Invoke(new YourDelegate(thisMethod));
    else
        mImageListView.RefreshInternal();
}

That way it would check one more time before finally calling RefreshInternal().

Bobby
A: 

may be lock(mImageListView){...} ?

alexm
This won't work. There's no guarantee that Disposed isn't called while you are inside the lock.
Isak Savo
+1 for saying what I was gonna say.
Jrud
@Isak The purpose of a lock is to block other threads from accessing an object. If he locks the object, then by definition, it cannot be disposed while locked.
Jrud
You lock the object on Thread2, but the object gets disposed on Thread1. So either you introduce a deadlock, or a no-op.
arul
@Jrud: That's not true. Lock just means that you block other threads from trying to acquire the *same lock*. It is still possible to call any methods on a "locked" object.
Isak Savo
may be it will just prevent the GC from disposing it it, i guess it should check the state of SyncBlockIndex ob object before doing any activities(in case object's Dispose isn't called by user's code)
alexm
+4  A: 

What you have here is a race condition. You're better off just catching the ObjectDisposed exception and be done with it. In fact, I think in this case it is the only working solution.

try
{
    if (mImageListView.InvokeRequired)
       mImageListView.Invoke(new YourDelegate(thisMethod));
    else
       mImageListView.RefreshInternal();
} 
catch (ObjectDisposedException ex)
{
    // Do something clever
}
Isak Savo
I was really hoping to avoid try/catching. But I'll do that if it is the only solution.
Ozgur Ozcitak
Well you *can* solve it by using mutex or locks, but it is much more error prone and can lead to weird bugs as the code is evolving. You'll need to protect all calls to Dispose() with the same mutex and that'll get harder as the code evolves...
Isak Savo
+4  A: 

There are implicit race conditions in your code. The control can be disposed between your IsDisposed test and the InvokeRequired test. There's another one between InvokeRequired and Invoke(). You can't fix this without ensuring the control outlives the life of the thread. Given that your thread is generating data for a list view, it ought to stop running before the list view disappears.

Do so by setting e.Cancel in the FormClosing event and signaling the thread to stop with a ManualResetEvent. When the thread completes, call Form.Close() again. Thread.Abort() is a distant second choice but much easier to implement. Using BackgroundWorker makes it easy to implement the thread completion logic.

Hans Passant
Isn't catching the `ObjectDisposedException` exception, as state below by Isak Savo, much cleaner than tinkering with the wicked Thread.Abort() or relying on the FormClosing event ?
arul
Can you guarantee it will only ever be an ObjectDisposedException? What if its a legitimate ObjectDisposedException? It's a rabbit-hole. Fix the problem, don't shoot the messenger.
Hans Passant
I ended up doing something similar to this. But instead of handling FormClosing, I overrode control's OnHandleDestroyed and blocked the UI thread while waiting for threads to exit.
Ozgur Ozcitak
This is normally a recipe for deadlock, a thread's completion callback can't run because the UI thread isn't pumping messages. Sounds like you managed to avoid it.
Hans Passant
Ouch. Keep banging on the X to make sure that works right. Your user certainly will.
Hans Passant
You are of course right. I can't say I'm proud of what I did to avoid the deadlock. I basically wrote something along the lines of: while(myThread.IsAlive) { Application.DoEvents(); } So I actually didn't block the UI thread, but merely prevented the Handle being destroyed until the worker threads are done.
Ozgur Ozcitak
Well, you are right but is that different from setting e.Cancel in FormClosing?
Ozgur Ozcitak
Yes, it doesn't recurse over and over again with bits of code executing after the recursion eventually returns. Mind you, I'm not saying it doesn't work, I'm just saying that it might byte you in the ass some day. I'm probably over-pessimistic, triggered by not seeing the code and the OP not using the suggested solutions. I trust you got it right. Even the soul that maintains your code after you leave might agree.
Hans Passant
You are probably right. As I said I'm not particularly proud of my solution. I wish there was an Control.InvokeIfYouCanOtherwiseIDontCare() method built in to the framework. Anyway, I'll reflect more on yours and others solutions. And here is the code if you'd be interested in checking it out: http://code.google.com/p/imagelistview/
Ozgur Ozcitak
What's needed to do things cleanly are Control.TryBeginInvoke and Control.TryInvoke methods. Given the lack of them, I think the best approach is to write such a function yourself. Munching exceptions is icky, but it avoids a lot of added complexity.
supercat
+1  A: 

You could use mutexes.

Somewhere at the start of the thread :

 Mutex m=new Mutex();

Then :

if (mImageListView != null && 
    mImageListView.IsHandleCreated &&
    !mImageListView.IsDisposed)
{
    m.WaitOne(); 

    if (mImageListView.InvokeRequired)
        mImageListView.Invoke(
            new RefreshDelegateInternal(mImageListView.RefreshInternal));
    else
        mImageListView.RefreshInternal();

    m.ReleaseMutex();
}

And whereever it is you are disposing of mImageListView :

 m.WaitOne(); 
 mImageListView.Dispose();
 m.ReleaseMutex();

This should ensure you cant dispose and invoke at the same time.

Mongus Pong
Isn't there still a race condition between the IsDisposed check and the WaitOne call?
Ozgur Ozcitak
Yes well spotted, you are right. You could either put another check for IsDisposed after the WaitOne call, or you could put the WaitOne before the IsDisposed check. I would choose the latter option if you anticipate IsDisposed to be false more times than true when the code is executed to save the extra call.
Mongus Pong
+1  A: 

See also this question:

Avoiding the woes of Invoke/BeginInvoke in cross-thread WinForm event handling?

The utility class that resulted EventHandlerForControl can solve this problem for event method signatures. You could adapt this class or review the logic therein to solve the issue.

The real problem here is that nobugz is correct as he points out that the APIs given for cross-thread calls in winforms are inherently not thread safe. Even within the calls to InvokeRequired and Invoke/BeginInvoke themselves there are several race conditions that can cause unexpected behavior.

csharptest.net