web-dev-qa-db-ja.com

オブジェクトの同期メソッドが、非同期のコードブロックから呼び出されました。 Mutex.Release()の例外

私はこの例外について別の記事を見つけましたが、それらはどれも私の事例ではありませんでした。ソースコードは次のとおりです。

_class Program
{

    private static Mutex mutex;
    private static bool mutexIsLocked = false;
    static void Main(string[] args)
    {

        ICrmService crmService = 
            new ArmenianSoftware.Crm.Common.CrmServiceWrapper(GetCrmService("Armsoft", "crmserver"));
        //Lock mutex for concurrent access to workflow
        mutex = new Mutex(true, "ArmenianSoftware.Crm.Common.FilterCtiCallLogActivity");
        mutexIsLocked = true;

        //Create object for updating filtered cti call log
        ArmenianSoftware.Crm.Common.FilterCtiCallLog filterCtiCallLog =
            new ArmenianSoftware.Crm.Common.FilterCtiCallLog(crmService);
        //Bind events
        filterCtiCallLog.CtiCallsRetrieved += new EventHandler<ArmenianSoftware.Crm.Common.CtiCallsRetrievedEventArgs>(filterCtiCallLog_CtiCallsRetrieved);

        //Execute filter
        try
        {
            filterCtiCallLog.CreateFilteredCtiCallLogSync();
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            if (mutexIsLocked)
            {
                mutexIsLocked = false;
                mutex.ReleaseMutex();
            }
        }
    }

    static void filterCtiCallLog_CtiCallsRetrieved(object sender,
         ArmenianSoftware.Crm.Common.CtiCallsRetrievedEventArgs e)
    {
        tryasasas
        {
            if (mutexIsLocked)
            {
                mutexIsLocked = false;
                mutex.ReleaseMutex();
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}
_

filterCtiCallLog.CreateFilteredCtiCallLogSync();関数はサーバーへのリクエストを実行し、いくつかのイベントを発生させます。そのうちの1つはCtiCallsRetrieveイベントです。そして、このイベントが発生したときにミューテックスを解放する必要があります。しかし、mutex.Release()関数を呼び出すと、例外がスローされます。 CreateFilteredCtiCallLogSyncは同期して動作します。何が問題ですか?

22
kyurkchyan

ミューテックスが所有されていることを示すboolを保持することは、Graveの間違いです。 boolをスレッドセーフにしません。間違った同期オブジェクトを使用しているため、このピクルに陥りました。ミューテックスにはスレッドアフィニティがあり、ミューテックスの所有者はスレッドです。それを取得したスレッドは、ReleaseMutex()を呼び出すスレッドでなければなりません。あなたのコードが爆弾になるのはそのためです。

おそらくeventが必要です。AutoResetEventを使用してください。メインスレッドで作成し、ワーカーでSet()を呼び出し、メインスレッドでWaitOne()を呼び出して、ワーカーがジョブを完了するのを待ちます。後で処分します。また、スレッドを使用してジョブを実行し、メインスレッドの完了を待機させることは生産的ではないことに注意してください。メインスレッドにジョブを実行させることもできます。

スレッドセーフではない(明確ではない)オブジェクトへのアクセスを保護するために実際にこれを行う場合は、lockステートメントを使用します。

40
Hans Passant

問題が見つかりました。 filterCtiCallLogクラスに関する最初のいくつかのこと。非同期と同期の両方で動作するように設計しました。最初に、非同期実行用のコードを作成しました。作業状態を報告するために、子ワーカースレッドから親にイベントをトリガーする方法が必要でした。このために、私は AsyncOperation クラスを使用し、それはpostメソッドです。 CtiCallsRetrievedイベントをトリガーするためのコード部分は次のとおりです。

_public class FilterCtiCallLog
{
    private int RequestCount = 0;
    private AsyncOperation createCallsAsync = null;
    private SendOrPostCallback ctiCallsRetrievedPost;
    public void CreateFilteredCtiCallLogSync()
    {
        createCallsAsync = AsyncOperationManager.CreateOperation(null);
        ctiCallsRetrievedPost = new SendOrPostCallback(CtiCallsRetrievedPost);
        CreateFilteredCtiCallLog();
    }

    private void CreateFilteredCtiCallLog()
    {
        int count=0;
        //do the job
        //............
        //...........
        //Raise the event
        createCallsAsync.Post(CtiCallsRetrievedPost, new CtiCallsRetrievedEventArgs(count));
        //...........
        //...........
    }

    public event EventHandler<CtiCallsRetrievedEventArgs> CtiCallsRetrieved;

    private void CtiCallsRetrievedPost(object state)
    {
        CtiCallsRetrievedEventArgs args = state as CtiCallsRetrievedEventArgs;
        if (CtiCallsRetrieved != null)
            CtiCallsRetrieved(this, args);
    }
}
_

ご覧のとおり、コードは同期的に実行されています。ここでの問題は、AsyncOperation.Post()メソッドにあります。メインスレッドで呼び出された場合、親スレッドにポストするのではなく、単にイベントをトリガーするように動作すると推測しました。しかし、そうではありませんでした。どのように動作するのかわかりませんが、コードを変更して、CreateFilteredCtiCallLogがsyncまたはasyncと呼ばれるかどうかを確認しました。非同期呼び出しの場合は_AsyncOperation.Post_メソッドを使用し、そうでない場合はEventHandlerでなければnullをトリガーしました。ここに修正されたコードがあります

_public class FilterCtiCallLog
{
    private int RequestCount = 0;
    private AsyncOperation createCallsAsync = null;
    private SendOrPostCallback ctiCallsRetrievedPost;
    public void CreateFilteredCtiCallLogSync()
    {
        createCallsAsync = AsyncOperationManager.CreateOperation(null);
        ctiCallsRetrievedPost = new SendOrPostCallback(CtiCallsRetrievedPost);
        CreateFilteredCtiCallLog(false);
    }

    private void CreateFilteredCtiCallLog(bool isAsync)
    {
        int count=0;
        //do the job
        //............
        //...........
        //Raise the event
        RaiseEvent(CtiCallsRetrievedPost, new CtiCallsRetrievedEventArgs(count),isAsync);
        //...........
        //...........
    }

    public event EventHandler<CtiCallsRetrievedEventArgs> CtiCallsRetrieved;

    private void RaiseEvent(SendOrPostCallback callback, object state, bool isAsync)
    {
        if (isAsync)
            createCallsAsync.Post(callback, state);
        else
            callback(state);
    }

    private void CtiCallsRetrievedPost(object state)
    {
        CtiCallsRetrievedEventArgs args = state as CtiCallsRetrievedEventArgs;
        if (CtiCallsRetrieved != null)
            CtiCallsRetrieved(this, args);
    }
}
_

答えてくれてありがとう!

5
kyurkchyan

この例外が発生する別の理由:

if (Monitor.TryEnter(_lock))
{
    try
    {
        ... await MyMethodAsync(); ...
    }
    finally
    {
        Monitor.Exit(_lock);
    }
}

「待機」の後、別のスレッドが実行を継続すると、Monitor.Exitでこの例外が発生します。

編集:スレッドを解放する必要がないため、SemaphoreSlimを使用します。

3
Igor Toropov

フラグを使用してカーネルシンクロオブジェクトの状態を監視しようとするだけでは機能しません。これらのシンクロコールを使用するポイントは、明示的なチェックを行わなくても正しく動作することです。フラグをチェックしてからアクションを実行するまでの割り込みにより、フラグが不適切に変更される可能性があるため、フラグを設定すると断続的な問題が発生します。

ミューテックスは、それを取得した脅威によってのみ解放できます。コールバックが別のスレッド(CreateFilteredCtiCallLogSync()の内部スレッドまたはカーネルスレッドプール)によって呼び出された場合、リリースは失敗します。

あなたが何をしようとしているのか明確ではありません。おそらく、CreateFilteredCtiCallLogSync()へのアクセスと、インスタンスが再利用可能なコールバックフラグへのアクセスをシリアル化する必要がありますか?もしそうなら、代わりにセマフォを使用することができます-init。それを1つのユニットに、開始時に待機し、コールバックでリリースします。

コールバックが呼び出されないことがあるため、try/finally/releaseが発生する問題はありますか?そうであれば、コールバックが非同期で、セットアップスレッドが関数を離れた後に別のスレッドによって呼び出される可能性がある場合、この方法は少し危険です。

3
Martin James

私はこれを1回か2回しか持っていませんでしたが、すべての場合、自分が所有していないミューテックスをリリースしようとすることで生じました。

ミューテックスが取得されたのと同じスレッドでイベントが発生しますか? filterCtiCallLog.CreateFilteredCtiCallLogSync()はブロッキング呼び出しであると言いますが、おそらくイベントを発生させるワーカースレッドが発生しますか?

2

Monitor。を使用してコードをロックし、非同期コードを呼び出してこれを取得すると、これが発生しましたが、lock(object)を使用するとコンパイラエラーが発生しますが、monitor.enter(object)とMonitor.Exist(object )コンパイラは文句を言いません...残念ながら。

1

最も意味のあるエラーメッセージではないかもしれませんが、以下のようなサードパーティのコードでこれが発生するのを見てきました。

object obj = new object();
lock (obj)
{
    //do something

    Monitor.Exit(obj);//obj released

}//exception happens here, when trying to release obj
0
Nemo