5

mTreeView と呼ばれる TreeView コントロールを持つ UserControl があります。複数の異なるスレッドからデータの更新を取得できます。これらにより、TreeView が更新されます。これを行うために、次のパターンを考案しました。すべてのデータ更新イベント ハンドラーは、ロックを取得してから、InvokeRequired をチェックする必要があります。その場合は、Invoke を呼び出して作業を行います。関連するコードは次のとおりです。

  public partial class TreeViewControl : UserControl
  {  
    object mLock = new object();
    void LockAndInvoke(Control c, Action a)
    {
      lock (mLock)
      {
        if (c.InvokeRequired)
        {
          c.Invoke(a);
        }
        else
        {
          a();
        }
      }
    }

    public void DataChanged(object sender, NewDataEventArgs e)
    {
      LockAndInvoke(mTreeView, () =>
        {
          // get the data
          mTreeView.BeginUpdate();
          // perform update
          mTreeView.EndUpdate();
        });
    }    
  }

私の問題は、起動時に mTreeView.BeginUpdate() で InvalidOperationException が発生することがあり、mTreeView が作成されたスレッドとは異なるスレッドから更新されているということです。LockAndInvoke のコール スタックに戻ると、見よ、c.InvokeRequired は true ですが、else ブランチが取得されました。それは、else ブランチが取得された後、別のスレッドで InvokeRequired が true に設定されたかのようです。

私のアプローチに何か問題がありますか?これを防ぐために何ができますか?

編集:私の同僚は、問題はコントロールが作成されるまで InvokeRequired が false であることであると私に言ったので、これが起動時に発生する理由です。しかし、彼はそれについて何をすべきかわからない. 何か案は?

4

3 に答える 3

7

スタンダードなスレッディングレースです。TreeView が作成される前に、スレッドを開始するのが早すぎます。そのため、コードは InvokeRequired を false として認識し、ネイティブ コントロールが作成された瞬間に失敗します。これを修正するには、フォームの Load イベント (すべてのコントロール ハンドルが有効であることを保証する最初のイベント) が発生したときにのみスレッドを開始します。

コードのいくつかの誤解。ロックを使用する必要はありません。InvokeRequired と Begin/Invoke はどちらもスレッドセーフです。InvokeRequired はアンチパターンです。ほとんどの場合、メソッドがワーカー スレッドによって呼び出されることはわかっています。したがって、InvokeRequired は、false の場合にのみ例外をスローするために使用してください。これにより、この問題を早期に診断できたはずです。

于 2012-07-13T16:06:16.523 に答える
2

UI スレッドにマーシャリングすると、それは 1 つのスレッドになり、一度に 1 つのことしか実行できなくなります。Invoke を呼び出すときにロックは必要ありません。

Invoke の問題は、呼び出しスレッドをブロックすることです。その呼び出しスレッドは通常、UI スレッドで何が完了したかを気にしません。その場合は、BeginInvoke を使用してアクションを非同期的に UI スレッドにマーシャリングすることをお勧めします。UI スレッドがバックグラウンド スレッドが何かを完了するのを待っている間、Invoke でバックグラウンド スレッドがブロックされ、最終的にデッドロックが発生する状況があります。次に例を示します。

private bool b;
public void EventHandler(object sender, EventArgs e)
{
  while(b) Thread.Sleep(1); // give up time to any other waiting threads
  if(InvokeRequired)
  {
    b = true;
    Invoke((MethodInvoker)(()=>EventHandler(sender, e)), null);
    b = false;
  }
}

... 上記は、Invoke が EventHandler への呼び出しが返されるまで返されず、EventHandler が b が false になるまで返されないため、while ループ while でデッドロックします...

コードの特定のセクションの実行を停止するために bool を使用していることに注意してください。これはロックとよく似ています。したがって、ロックを使用するとデッドロックが発生する可能性があります。

これを行うだけです:

public void DataChanged(object sender, NewDataEventArgs e)
{
      if(InvokeRequired)
      {
          BeginInvoke((MethodInvoker)(()=>DataChanged(sender, e)), null);
          return;
      }
      // get the data
      mTreeView.BeginUpdate();
      // perform update
      mTreeView.EndUpdate();
}

これは、UI スレッドで DataChanged メソッドを非同期的に再度呼び出すだけです。

于 2012-07-13T15:49:16.640 に答える
1

上で示したパターンは、私には100%問題ないように見えます(余分な不必要なロックがありますが、これがあなたが説明した問題をどのように引き起こすかわかりません)。

David W が指摘しているように、実行していることとこの拡張メソッドmTreeViewの唯一の違いは、アクションに引数として渡すのではなく、UI スレッドに直接アクセスすることですが、これは、mTreeViewいずれにせよ、これを取得して説明した問題を引き起こすには、かなり努力する必要があります。

つまり、問題は別のものでなければなりません。

私が考えることができる唯一のことはmTreeView、UI スレッド以外のスレッドで作成した可能性があるということです。この場合、ツリー ビューへのアクセスは 100% 安全ですが、そのツリー ビューを別のスレッドで作成されたフォームを使用すると、説明したものと同様の例外が発生します。

于 2012-07-13T15:46:27.323 に答える