The reality is that with Invoke and friends you cannot fully protect against invoke on the remote component and then get an InvalidOperationException due to the lack of a descriptor. I havnt really saw the answer yet, like the one below, in any of the threads, which touches on a fundamental problem that cannot be completely solved by selective testing or using lock semantics.
Here's the normal "right" idiom:
// the event handler. in this case preped for cross thread calls void OnEventMyUpdate(object sender, MyUpdateEventArgs e) { if (!this.IsHandleCreated) return; // ignore events if we arn't ready, and for // invoke if cant listen to msg queue anyway if (InvokeRequired) Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData); else this.MyUpdate(e.MyData); } // the update function void MyUpdate(Object myData) { ... }
Fundamental problem:
When using the Invoke tool, the Windows message queue is used, which puts the message in the queue to either wait or fire and forget about the cross-flow, just like a Mail or Send message. If a message appears before the Invoke message that will invalidate the component and its window handle, or will be placed immediately after any checks that you are trying to perform, then you will have a bad time.
x thread -> PostMessage(WM_CLOSE); // put 'WM_CLOSE' in queue y thread -> this.IsHandleCreated // yes we have a valid handle y thread -> this.Invoke(); // put 'Invoke' in queue ui thread -> this.Destroy(); // Close processed, handle gone y thread -> throw Invalid....() // 'Send' comes back, thrown on calling thread y
There is no real way to find out that the control is about to remove itself from the queue, and you cannot do anything reasonable to βcancelβ the call. No matter how many checks you make or what additional locks you make, you cannot stop someone else by issuing something like closing or deactivating. There are tons of senaria where this can happen.
Decision:
The first thing to understand is that the call will fail, no different from how the check (IsHandleCreated) would ignore the event. If the goal is to protect the caller from a thread other than the UI, you will need to handle the exception and treat it like any other call that fails (so that the application does not hang or does anything. And if you don't rewrite / reeroll Invoke Your catch is the only way to find out.
// the event handler. in this case preped for cross thread calls void OnEventMyWhatever(object sender, MyUpdateEventArgs e) { if (!this.IsHandleCreated) return; if (InvokeRequired) { try { Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData); } catch (InvalidOperationException ex) // pump died before we were processed { if (this.IsHandleCreated) throw; // not the droids we are looking for } } else { this.MyUpdate(e.MyData); } } // the update function void MyUpdate(Object myData) { ... }
Exception filtering can be tailored to suit your needs. Itβs good to know that workflows often do not have all the soft external handling of exceptions and registration of user interface threads in most applications, so you can simply gobble up any exception on the worker side. Or record and flip all of them. For many exceptions thrown from the workflow, it means the application will crash.