2012-01-28 7 views
6

C#でマルチスレッドに問題があります。 イベントを使用して、別のスレッドからフォームのラベルを更新します。そのためには、もちろんInvoke()コマンドを使用する必要があります。 その部分もうまくいきます。 しかし、ユーザーはフォームを閉じることができます。イベントが残念ながら送信された場合、プログラムはクラッシュする可能性があります。ロックされたオブジェクトが1つのみの「デッドロック」ですか?

フォームのDispose()メソッドをオーバーライドし、ロックされたコード内でブール値をtrueに設定し、ブール値をチェックし、ロックされたコードでイベントを呼び出すと考えました。

しかし、私がフォームを閉じるたびに、プログラムは完全にフリーズします。

は、ここでは、コードの言及部分です:

private object dispose_lock = new object(); 
private bool _disposed = false; 

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     lock (dispose_lock) 
     { 
      if (_disposed) return; 
      Invoke(handler); // this is where it crashes without using the lock 
     } 
     return; 
    } 

    label.Text = "blah"; 
} 

protected override void Dispose(bool disposing) 
{ 
    eventfullObject.OnUpdate -= update; 
    lock (dispose_lock) // this is where it seems to freeze 
    { 
     _disposed = true; // this is never called 
    } 
    base.Dispose(disposing); 
} 

私はここに誰もがこのコードで何が間違っている任意のアイデアを持っている願っています。 ありがとうございます!

+0

実際のアプリケーションで更新呼び出しがウィンドウが廃棄させてもらえますか?その場合、バックグラウンドスレッドはロックを持つ可能性があり、UIスレッドは、バックグラウンドスレッドが保持しているのと同じオブジェクトに対してDisposeロックに終わる可能性があります。 –

+1

どこから変数InvokeRequiredを取得しているのですか?更新するコントロールで呼び出す必要があります:if(label.InvokeRequired){//} – Lloyd

答えて

1

私は本当にここで簡単に行くだろう。トリッキーなスレッドセーフなコードを実装する代わりに、私は単に例外をキャッチし、失敗した場合は何もしません。

try 
{ 
    this.Invoke(Invoke(handler)); 
} 
catch (ObjectDisposedException) 
{ 
    // Won't do anything here as 
    // the object is not in the good state (diposed when closed) 
    // so we can't invoke. 
} 

それは簡単で、非常に簡単です:それはObjectDisposedExceptionだと仮定すると、

。コメントにと指定した場合、なぜが例外を捕まえるのですか?OKです。

+1

悪い考えIMO ... – CodesInChaos

+0

@CodeInChaosロックの複雑さ、Dispose ...のオーバーライドなど。単に例外をキャッチするよりも優れています。たぶんあなたはそれが悪い考えだと思う理由を説明することができます** – ken2k

+0

同じ意見:http://stackoverflow.com/a/1874785/870604 – ken2k

6

あなたが考慮していないのは、Invokeに渡されたデリゲートがUIスレッドで非同期に呼び出されるということです。 Invokeを呼び出すと、フォームメッセージキューにメッセージがポストされ、しばらくして取得されます。

は何が起こるかではありません。代わりに

UI Thread     Background Thread 
          Call update() 
          take lock 
          Call Invoke() 
Call update()    
          release lock 
Call Dispose() 
take lock 
release lock 

しかし:このための

UI Thread     Background Thread 
          Call update() 
           take lock 
           Call Invoke() 
           block until UI Thread processes the message 
Process messages 
... 
Dispose() 
    wait for lock ****** Deadlock! ***** 
... 
Call update()    
          release lock 

、バックグラウンドスレッドがUIスレッドがDispose

を実行しようとしている間にロックを保持する保持することができます

解決策は、試したものよりずっと簡単です。 Invokeが非同期にポストされるため、ロックの必要はありません。

private bool _disposed = false; 

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     Invoke(handler); 
     return; 
    } 

    if (_disposed) return; 

    label.Text = "blah"; 
} 

protected override void Dispose(bool disposing) 
{ 
    eventfullObject.OnUpdate -= update; 
    _disposed = true; // this is never called 
    base.Dispose(disposing); 
} 

_disposedフラグは、UIスレッドでのみ読み書きされるため、ロックする必要はありません。今、あなたは、スタックのように見える呼び出す:

UI Thread     Background Thread 
          Call update() 
           take lock 
           Call Invoke() 
           block until UI Thread processes the message 
Process messages 
... 
Dispose() 
    _disposed = true; 
... 

Call update() 
    _disposed is true so do nothing    
+1

'Invoke'呼び出しは常に失敗することがあります。このコードでは、コントロールが配置されている正確な時刻にInvokeが呼び出されたときに、妨げられない例外が発生します。 – JaredPar

+0

呼び出しは常に同期です。 – usr

+0

@usr私が説明しようとしたのは、InvokeがUIスレッドに関して非同期であるということでした。バックグラウンドスレッドと同期しています。私は私の答えをより明確にしようとします – shf301

0

IMO Disposeが遅すぎる...

私はDisposeは、私の知る限りを発生する前に呼び出されFormClosingにいくつかのコードを置くことをお勧めします。

このような場合は、通常、チェックのために別の(アトミック)パターンを使用する傾向があります。たとえば、Interlockedクラスを使用します。

private long _runnable = 1; 

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     if (Interlocked.Read (ref _runnable) == 1) Invoke(handler); 
     return; 
    } 

    label.Text = "blah"; 
} 

FormClosingであなただけのInterlocked.Increment (ref _runnable)を呼び出します。

0

ただ他の答えのいずれも、犯人ではない、投稿されていないスレッドを終了する他のコードはありますか?私はあなたがプレーンなスレッドを使用している可能性が考えていないのBackgroundWorker、およびControl.Invokeを使用しての危険性の一つは、それがのように不幸な時にUIスレッド上に配置することができることである真

1

Thread.isBackroundを設定し忘れているかもしれませんよあなたは示唆しました。あなたは、イベント

  1. バックグラウンドスレッドの次の順序を持​​っているとき、この問題が発生した最も一般的な方法は次のとおりです。起動
  2. フォアグラウンドスレッドでコールバックをキュー:背景が起動
  3. フォアグラウンドスレッドと呼ばれているコントロールを配置します:廃棄されたコントロールのコールをデキューします。

このシナリオでは、Invokeが失敗し、バックグラウンドスレッドで例外が発生します。これはおそらくアプリケーションが最初にクラッシュする原因になります。

新しいコードではデッドロックが発生しますが、手順1でコードがロックされます。その後、ステップ#2のUIで処理が行われ、ステップ#3が完了するまで解放されないロックが待機しています。このウォン -

この問題に対処する最も簡単な方法は、Invokeは、したがって、失敗することができ、操作がtry/catch

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     try 
     { 
      Invoke(handler); 
     } 
     catch (Exception) 
     { 
      // Control disposed while invoking. Nothing to do 
     } 
     return; 
    } 

    label.Text = "blah"; 
} 
1

なぜあなただ​​けの呼び出しではなく、BeginInvokeメソッドを使用していないが必要であることを受け入れることですバックグラウンドスレッドをブロックしません。バックグラウンドスレッドがUIの更新はあなたが WPFアプリケーションで(Dispatcher.Invokeを呼び出すときに別のデッドロックシナリオが発生する

1

示したものから発生するのを待つ必要がある理由は、特定の理由があるようです見えません)またはControl.Invoke(Windowsフォームアプリケーションの場合) ロックを保持している間。同じロックで待機している別の メソッドがUIで実行されている場合は、デッドロックが発生します。 これはInvokeの の代わりにBeginInvokeを呼び出すだけで解決できることがよくあります。または、 呼び出しを呼び出す前にロックを解除することもできますが、呼び出し元がロックを解除した場合は機能しません。我々は リッチクライアントアプリケーションとスレッドの呼び出しとBeginInvokeを説明します アフィニティ。

ソース:http://www.albahari.com/threading/part2.aspx

関連する問題