-->

Is there a variant of Control.BeginInvoke which wo

2019-06-25 13:17发布

问题:

I have a control which displays the state of an underlying asynchronous object. The object raises events, which arrive at the form, where they are essentially queued and eventually called using BeginInvoke.

A problem arises when the control is disposed. Because things are happening asynchronously, meaning it is always possible that an event callback is queued during disposal, I sometimes get an InvalidOperationException (Invoke or BeginInvoke cannot be called on a control until the window handle has been created.).

This is not the behavior I want. I want the callback to execute even if the control has been disposed (even if that causes an exception in the callback; that's a far more useful exception to me!). I want to handle the disposed state behavior in each callback (usually just skip if disposed, but sometimes not [eg. one control logs events (optionally to a file) and I don't want to lose log data!].).

Is there a method that works the way I want? Can I write a non-brittle one myself?

回答1:

Try SynchronizationContext.Current instead. This has the Post and Send members which roughly map to BeginInvoke and Invoke on Control. These operations will continue to function so long as the UI thread is alive vs. a specific control.

The type SynchronizationContext is not specific to WinForms and solutions leveraging it will be portable to other frameworks such as WPF.

For example.

BeginInvoke Code

void OnButtonClicked() {
  DoBackgroundOperation(this); 
}

void DoBackgroundOperation(ISynchronizedInvoke invoke) {
  ThreadPool.QueueUserWorkItem(delegate { 
    ...
    delegate.BeginInovke(new MethodInvoker(this.BackgroundOperationComplete), null);
  });
}

SynchronizationContext code

void OnButtonClicked() {
  DoBackgroundOperation(SynchronizationContext.Current);
}

void DoBackgroundOperation(SynchronizationContext context) {
  ThreadPool.QueueUserWorkItem(delegate {
    ...
    context.Post(delegate { this.BackgroundOperationComplete() }, null);
  });
}