28

ワーカー スレッドに次のコードがあります (ImageListView以下は から派生したものですControl)。

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

ただし、上記の方法でObjectDisposedException時々取得します。コントロールは、チェックしてから呼び出すInvokeまでの間に破棄できるようです。どうすればそれを回避できますか?IsDisposedInvoke

4

14 に答える 14

19

ここにあるのは競合状態です。ObjectDisposed 例外をキャッチして、それで完了したほうがよいでしょう。実際、この場合、それが唯一の有効な解決策だと思います。

try
{
    if (mImageListView.InvokeRequired)
       mImageListView.Invoke(new YourDelegate(thisMethod));
    else
       mImageListView.RefreshInternal();
} 
catch (ObjectDisposedException ex)
{
    // Do something clever
}
于 2009-12-09T15:47:56.613 に答える
14

コードに暗黙の競合状態があります。コントロールは、IsDisposed テストと InvokeRequired テストの間に配置できます。InvokeRequired と Invoke() の間にもう 1 つあります。コントロールがスレッドの寿命を超えて存続することを確認せずに、これを修正することはできません。スレッドがリスト ビューのデータを生成していることを考えると、リスト ビューが消える前に実行を停止する必要があります。

これを行うには、FormClosing イベントで e.Cancel を設定し、ManualResetEvent で停止するようにスレッドに通知します。スレッドが完了したら、 Form.Close() を再度呼び出します。BackgroundWorker を使用すると、スレッド完了ロジックを簡単に実装できます。この投稿でサンプル コードを見つけてください。

于 2009-12-09T15:52:23.320 に答える
4

現実には、Invoke とその仲間では、破棄されたコンポーネントでの呼び出し、またはハンドルの欠落による InvalidOperationException の取得を完全に保護することはできません。プリエンプティブ テストまたはロック セマンティクスを使用しても完全に解決できない、実際の根本的な問題に対処するスレッドのいずれかで、以下のような答えをまだ実際に見ていません。

通常の「正しい」イディオムは次のとおりです。

// 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)
{
    ...
}

根本的な問題:

Invoke 機能を使用する場合、Windows メッセージ キューが使用されます。これは、Post または Send メッセージとまったく同じように、メッセージをキューに配置して、クロス スレッド呼び出しを待機するか、ファイア アンド フォーゲットします。Invoke メッセージの前に、コンポーネントとそのウィンドウ ハンドルを無効にするメッセージがある場合、または実行しようとしたチェックの直後にメッセージが配置された場合、問題が発生します。

 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

コントロールが自分自身をキューから削除しようとしていることを知る実際の方法はなく、呼び出しを「元に戻す」ためにできることは本当に合理的ではありません。何度チェックしたり、追加のロックを作成したりしても、他の誰かがクローズや非アクティブ化のようなものを発行するのを止めることはできません。これが発生するシナリオはたくさんあります。

解決策:

最初に認識すべきことは、(IsHandleCreated) チェックがイベントを無視した場合と同じように、呼び出しが失敗するということです。目標が非 UI スレッドで呼び出し元を保護することである場合は、例外を処理し、成功しなかった他の呼び出しと同様に処理する必要があります (アプリがクラッシュしないようにするため、またはその他のことを行うため)。リロール 機能を呼び出します。キャッチは、知る唯一の方法です。

// 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)
{
    ...
}

例外フィルタリングは、ニーズに合わせて調整できます。ほとんどのアプリケーションでは、多くの場合、ワーカー スレッドには、UI スレッドが行う便利な外部例外処理とログ記録がすべて備わっているわけではないことに注意してください。または、それらすべてをログに記録して再スローします。多くの場合、ワーカー スレッドでキャッチされない例外は、アプリがクラッシュすることを意味します。

于 2013-09-05T22:41:40.680 に答える
3

使ってみて

if(!myControl.Disposing)
    ; // invoke here

私はあなたとまったく同じ問題を抱えていました。コントロールで .Disposing をチェックするように切り替えて以来、ObjectDisposedException はなくなりました。これで 100% の確率で修正されるとは言いませんが、99% です ;) 破棄のチェックと呼び出しの呼び出しの間に競合状態が発生する可能性はまだありますが、私が行ったテストでは実行していません。それに入れます (私は ThreadPool とワーカー スレッドを使用します)。

呼び出す各呼び出しの前に使用するものは次のとおりです。

    private bool IsControlValid(Control myControl)
    {
        if (myControl == null) return false;
        if (myControl.IsDisposed) return false;
        if (myControl.Disposing) return false;
        if (!myControl.IsHandleCreated) return false;
        if (AbortThread) return false; // the signal to the thread to stop processing
        return true;
    }
于 2012-10-30T16:44:00.337 に答える
1

この質問も参照してください:

クロススレッドの WinForm イベント処理で Invoke/BeginInvoke の問題を回避するには?

EventHandlerForControlの結果であるユーティリティ クラスは、イベント メソッド シグネチャに関するこの問題を解決できます。このクラスを調整するか、その中のロジックを確認して、問題を解決できます。

ここでの本当の問題は、nobugz が正しいことです。彼は、winform でのクロススレッド呼び出し用に提供された API は本質的にスレッド セーフではないことを指摘しています。InvokeRequired および Invoke/BeginInvoke 自体の呼び出し内でも、予期しない動作を引き起こす可能性のある競合状態がいくつかあります。

于 2009-12-09T17:04:51.140 に答える
1

フォームを閉じるイベントを処理します。オフ UI スレッドの作業がまだ発生しているかどうかを確認します。発生している場合は停止を開始し、終了イベントをキャンセルしてから、フォーム コントロールで BeginInvoke を使用して終了を再スケジュールします。

private void Form_FormClosing(object sender, FormClosingEventArgs e)
{
    if (service.IsRunning)
    {
        service.Exit();
        e.Cancel = true;
        this.BeginInvoke(new Action(() => { this.Close(); }));
    }
}
于 2011-09-06T21:05:09.350 に答える
1

BackGroundWorker の可能性がある場合、これを回避する非常に簡単な方法があります。

public partial class MyForm : Form
{
    private void InvokeViaBgw(Action action)
    {
        BGW.ReportProgress(0, action);
    }

    private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        if (this.IsDisposed) return; //You are on the UI thread now, so no race condition

        var action = (Action)e.UserState;
        action();
    }

    private private void BGW_DoWork(object sender, DoWorkEventArgs e)
    {
       //Sample usage:
       this.InvokeViaBgw(() => MyTextBox.Text = "Foo");
    }
}
于 2010-11-15T23:55:34.797 に答える
1

lock(mImageListView){...} である可能性がありますか?

于 2009-12-09T15:45:09.320 に答える
1

ミューテックスを使用できます。

スレッドの最初のどこか:

 Mutex m=new Mutex();

それで :

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

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

    m.ReleaseMutex();
}

そして、どこにいても mImageListView を処分しています:

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

これにより、破棄と呼び出しを同時に行うことができなくなります。

于 2009-12-09T15:55:15.310 に答える
1

Isak Savo によって提案されたソリューション

try
  {
  myForm.Invoke(myForm.myDelegate, new Object[] { message });
  }
catch (ObjectDisposedException)
  { //catch exception if the owner window is already closed
  }

C# 4.0 では動作しますが、何らかの理由で C#3.0 では失敗します (とにかく例外が発生します)

そのため、フォームが閉じているかどうかを示すフラグに基づいて別のソリューションを使用し、その結果、フラグが設定されている場合は呼び出しを使用できなくなりました

   public partial class Form1 : Form
   {
    bool _closing;
    public bool closing { get { return _closing; } }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        _closing = true;
    }

 ...

 // part executing in another thread: 

 if (_owner.closing == false)
  { // the invoke is skipped if the form is closing
  myForm.Invoke(myForm.myDelegate, new Object[] { message });
  }

これには、try/catch の使用を完全に回避できるという利点があります。

于 2011-10-16T12:17:37.593 に答える
0

1 つの方法は、ImageListView-Method を呼び出す代わりに、メソッド自体をもう 1 つ呼び出すことです。

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

そうすれば、最終的に RefreshInternal() を呼び出す前にもう一度チェックします。

于 2009-12-09T15:43:07.810 に答える