web-dev-qa-db-ja.com

廃棄したときにMutexがリリースされないのはなぜですか?

私は次のコードを持っています:

using (Mutex mut = new Mutex(false, MUTEX_NAME))
{
    if (mut.WaitOne(new TimeSpan(0, 0, 30)))
    {
       // Some code that deals with a specific TCP port
       // Don't want this to run at the same time in another process
    }
}

ifブロック内にブレークポイントを設定し、Visual Studioの別のインスタンス内で同じコードを実行しました。予想どおり、.WaitOne呼び出しはブロックします。しかし、驚いたことに、最初のインスタンスでcontinueになり、usingブロックが終了するとすぐに、放棄されたミューテックスに関する2番目のプロセスで例外が発生します。

修正はReleaseMutexを呼び出すことです:

using (Mutex mut = new Mutex(false, MUTEX_NAME))
{
    if (mut.WaitOne(new TimeSpan(0, 0, 30)))
    {
       // Some code that deals with a specific TCP port
       // Don't want this to run twice in multiple processes
    }

    mut.ReleaseMutex();
}

今、物事は期待どおりに機能します。

私の質問:通常IDisposableのポイントはそれですcleans upどんな状態に入れても構いません。おそらく複数のwaitsおよびreleasesusingブロック内にありますが、Mutexのハンドルが破棄されると、自動的に解放されるべきではありませんか?つまり、ReleaseMutexブロックにいる場合にusingを呼び出す必要があるのはなぜですか?

また、ifブロック内のコードがクラッシュした場合、ミューテックスが破棄されることを心配しています。

Mutexブロックにusingを入れることには利点がありますか?または、単にnew up a Mutexインスタンスをtry/catchにラップし、finallyブロック内でReleaseMutex()を呼び出す必要があります(基本的に私thoughtDispose()が行うことを正確に実装します)

52

ドキュメンテーション は、instantiatingMutexの間に概念的な違いがあることを(「解説」セクションで)説明しています。オブジェクト(これはnotを実行し、実際には同期が行われる限り特別なことを行います)およびacquiringMutex( WaitOne )。ご了承ください:

  • WaitOneはブール値を返します。つまり、Mutexを取得するとfail(タイムアウト)になり、両方のケースを処理する必要があります。
  • WaitOnetrueを返す場合、呼び出しスレッドはMutexを取得しており、must call ReleaseMutexを取得していない場合、Mutexは破棄されます。
  • falseを返す場合、呼び出しスレッドする必要はありません call ReleaseMutex

したがって、インスタンス化だけでなく、ミューテックスにもあります。とにかくusingを使用する必要があるかどうかについては、Disposeが何をするのかを見てみましょう( から継承されるWaitHandle ):

_protected virtual void Dispose(bool explicitDisposing)
{
    if (this.safeWaitHandle != null)
    {
        this.safeWaitHandle.Close();
    }
}
_

ご覧のとおり、Mutexはnotでリリースされていますが、何らかのクリーンアップが含まれているため、usingを使用することをお勧めします。

続行方法については、もちろん_try/finally_ブロックを使用して、Mutexが取得された場合に適切に解放されるようにすることができます。これはおそらく最も簡単なアプローチです。

really Mutexの取得に失敗した場合(TimeSpanWaitOneに渡すため指定していない)を気にしない場合は、独自のクラスでMutexをラップできます。 IDisposableを実装し、コンストラクターでMutexを取得し(引数なしでWaitOne()を使用して)、Dispose内で解放します。ただし、これはお勧めしません。これは、何か問題が発生した場合にスレッドを無期限に待機させ、取得の試行時に両方のケースを明示的に処理する 正当な理由 、@ HansPassantで述べたように。

53
voithos

この設計上の決定は、ずいぶん前になされました。 21年以上前、.NETが構想されるか、IDisposableのセマンティクスが考慮されるずっと前に。 .NET Mutexクラスは、ミューテックスの基盤となるオペレーティングシステムサポート用のwrapperクラスです。コンストラクターは CreateMutex をpinvokesし、WaitOne()メソッドは WaitForSingleObject() をpinvokesします。

WaitForSingleObject()のWAIT_ABANDONED戻り値に注意してください。これが例外を生成するものです。

Windows設計者は、mutexmustを所有するスレッドが終了する前にReleaseMutex()を呼び出すという堅固なルールを定めました。そして、これがスレッドが予期しない方法で、通常は例外を介して終了したという非常に強力な兆候であるということではない場合。これは、同期が失われることを意味します。これは非常に深刻なスレッド化バグです。同じ理由で.NETのスレッドを終了させる非常に危険な方法であるThread.Abort()と比較してください。

.NETデザイナーは、この動作を一切変更しませんでした。少なくとも、待機を実行する以外にミューテックスの状態をテストする方法がないためです。あなたはReleaseMutex()を呼び出す必要があります。また、2番目のスニペットも正しくないことに注意してください。取得していないミューテックスで呼び出すことはできません。 if()ステートメント本体内に移動する必要があります。

35
Hans Passant

ミューテックスの主な用途の1つは、不変条件を満たさない状態の共有オブジェクトを見る唯一のコードが、オブジェクトをその状態にする(できれば一時的に)コードであることを保証することです。オブジェクトを変更する必要があるコードの通常のパターンは次のとおりです。

  1. ミューテックスを取得する
  2. オブジェクトの状態を無効にする変更をオブジェクトに加える
  3. オブジェクトを変更して、その状態を再び有効にします
  4. ミューテックスを解放する

#2の開始後、#3の終了前に何か問題が発生した場合、オブジェクトは不変条件を満たさない状態のままになることがあります。適切なパターンは、ミューテックスを解放する前にミューテックスを解放することなので、コードがミューテックスを解放せずに処分するという事実は、どこかで問題が発生したことを意味します。そのため、(リリースされていないので)コードがミューテックスに入るのは安全ではないかもしれませんが、ミューテックスがリリースされるのを待つ理由はありません(破棄されているため、決して破棄されないため) 。したがって、適切なアクションは例外をスローすることです。

.NETミューテックスオブジェクトによって実装されるパターンよりもやや優れたパターンは、「取得」メソッドに、ミューテックスではなく、その特定の取得をカプセル化するIDisposableオブジェクトを返すことです。そのオブジェクトを破棄すると、ミューテックスが解放されます。コードは次のようになります。

using(acq = myMutex.Acquire())
{
   ... stuff that examines but doesn't modify the guarded resource
   acq.EnterDanger();
   ... actions which might invalidate the guarded resource
   ... actions which make it valid again
   acq.LeaveDanger();
   ... possibly more stuff that examines but doesn't modify the resource
}

EnterDangerLeaveDangerの間で内部コードが失敗した場合、取得オブジェクトはDisposeを呼び出してmutexを無効にする必要があります。他の場所で内部コードが失敗した場合、保護されたリソースは有効な状態であるため、ミューテックスを解放する必要があり、usingブロック内のコードはこれにアクセスする必要がなくなります。そのパターンを実装するライブラリの特定の推奨事項はありませんが、他の種類のミューテックスのラッパーとして実装することは特に難しくありません。

7
supercat

OK、私の質問への回答を投稿します。私が知ることができることから、thisMutexを実装する理想的な方法です:

  1. 常に処分される
  2. WaitOneが成功した場合に限り、リリースされます。
  3. コードが例外をスローしても放棄されません。

これが誰かの助けになることを願っています!

_using (Mutex mut = new Mutex(false, MUTEX_NAME))
{
    if (mut.WaitOne(new TimeSpan(0, 0, 30)))
    {
        try
        {
           // Some code that deals with a specific TCP port
           // Don't want this to run twice in multiple processes        
        }
        catch(Exception)
        {
           // Handle exceptions and clean up state
        }
        finally
        {
            mut.ReleaseMutex();
        }
    }
}
_

更新:tryブロック内のコードがリソースを不安定な状態にした場合、notMutexをリリースし、代わりに放棄させます。言い換えると、コードが正常に終了したときにmut.ReleaseMutex();を呼び出すだけで、finallyブロック内に配置しないでください。 Mutexを取得するコードは、この例外をキャッチし、正しいことを行います

私の状況では、私は本当に状態を変えていません。私は一時的にTCPポートを使用しています。プログラムの別のインスタンスを同時に実行することはできません。このため、上記のソリューションは問題ないと思いますが、異なる場合があります。

7

MSDNページの最初に何が起こっているのかを知るには、.netよりも多くを理解する必要があります。

interprocess同期にも使用できる同期プリミティブ。

MutexはWin32Named Object”であり、各プロセスはそれをロックしますby name、. netオブジェクトはWin32呼び出しの単なるラッパーです。 Muxtex自体は、アプリケーションのアドレス空間ではなく、Windows Kernalアドレス空間内に存在します。

ほとんどの場合、単一プロセス内のオブジェクトへのアクセスのみを同期しようとしている場合は、 Monitor を使用する方が適切です。

4
Ian Ringrose

ミューテックスが解放されることを保証する必要がある場合は、try catch finallyブロックに切り替えて、mutexリリースをfinallyブロックに入れます。ミューテックスの所有者であり、ハンドルを持っていることを前提としています。リリースが呼び出される前に、そのロジックを含める必要があります。

3
Mike Beeler

注意:ガベージコレクションプロセスはWindowsによるハンドルを所有していないため、ガベージコレクタによって実行されるMutex.Dispose()は失敗します。

2

ReleaseMutexのドキュメントを読むと、Mutexを意識的にリリースする必要があるという設計上の決定があったようです。 ReleaseMutexが呼び出されない場合、保護セクションの異常終了を意味します。リリースを最終的にまたは破棄することで、このメカニズムを回避します。もちろん、AbandonedMutexExceptionを無視してもかまいません。

2
ths

Disposeは、リリースされるWaitHandleに依存します。したがって、usingDisposeを呼び出しますが、安定状態の条件が満たされるまで影響を及ぼしません。 ReleaseMutexを呼び出すと、リソースを解放していることをシステムに伝えているため、自由に廃棄できます。

1
B.K.