1

1 か月の長い休憩の後、昨日もプログラムの作業を続けました。コードは何も変更していませんが、アプリケーションが起動しなくなりました。ある時点で実行が中断され、デッドロックに陥っているように見えますが、メソッドが戻ったときに発生するため、実際にデッドロックであるかどうかはわかりません-通常は発生しないはずです.

コードが大きいのでお見せできません。しかし、それ自体のスレッドを超えた唯一のアクションは、Dispatcher によって呼び出されるいくつかの UI 要素へのアクセスであると断言できます。そして、昨日まですべてがうまくいきました。何も変更していません。

これはそれが起こる場所です:

    internal override Task InitializeAddIns()
    {
        try
        {
            Action action = () => this._addinProvider.InitializeAddins();
            Task t = Task.Factory.StartNew(action);
            return t;
        }
        catch (Exception ex)
        {
            Debugger.Break();
            return null;
        }
    }

呼び出しコード:

// Initialize AddIns
splash.SplashText = "SplashScreen:step_searchAddIns".Translate();
this._addinSystem.InitializeAddIns();
splash.SplashText = "SplashScreen:step_startAddIns".Translate();
await Task.Run(() => this._addinSystem.RunAddins());

// Resolve libraries with NativeCompressor
splash.SplashText = "SplashScreen:step_resolveDependencies".Translate();

タスクが開始され、't' が返されます。InitializeAddins() メソッドは正常に実行されて終了します (デバッガーでチェック - ログも完全に終了したことを示しています)。次のステップは、「アクション」の宣言行がマークされていることです (終了したように)。その後、デバッグが終了し、それ以上何も起こりません。この Dispatcher フックでさえ呼び出されません。

Dispatcher.CurrentDispatcher.Hooks.DispatcherInactive += (sender, args) => this.Update();

私の唯一の仮定は、どこかにデッドロックがあるということです。それ以外の場合、実行全体が停止してスタックする理由を説明できません。どこから検索を開始すればいいのか、手がかりが見つかりません。新しく導入されたコードを作り直し、デッドロックも検出するいくつかの拡張ロック メソッドを追加しました。今のところデッドロックは検出されていません。

原因がわからないので、WinDbg と SOSEX を使ってエラーの原因を探ってみました。残念ながら、WinDbg を実行できません。Symbol サーバーをチェックし、最後の出力は次のとおりです。

CLRDLL: mscorwks 検索で mscordacwks_AMD64_x86_4.0.30319.34209.dll が見つかりません CLRDLL: パス上に 'SOS_AMD64_x86_4.0.30319.34209.dll' が見つかりません: 読み込まれた DLL mscordacwks_AMD64_x86_4.0.30319.34209.dll

明らかに何かがロードされていますが、SOSEX の !dlk コマンドを呼び出すと、次のメッセージが表示されます。

0:028> !dlk .NET データ インターフェイスを初期化できません。mscordacwks.dll のバージョン 4.0.30319.34209 が必要です。正しいバージョンの mscordacwks.dll を見つけてロードします。.cordll コマンドのドキュメントを参照してください。CriticalSections を調べています... デッドロックは検出されませんでした。

したがって、このバグを修正する方法がこれ以上わかりません。この行動の理由は何ですか?例外すらありません。既に CLR 例外を有効にしていますが、それらでさえスローされません。非常に奇妙です。通常、このロックダウンは、メソッドが終了した後ではなく、途中で発生すると予想されます...

4

3 に答える 3

1

この問題の原因を見つけました。現在のステータス (ロードされるアドインなど) を更新するためにこれらのメソッドによってアクセスされるのは、単純なウィンドウである私のスプラッシュスクリーンでした。これは絶対にスレッドセーフではありませんでした (なぜ以前は機能していたのだろうか...)。

すべてのプロパティで次のコードに変更しました。少しトリッキーに見えるので、そのコードをチェックして、ハッキングされていないか、悪いアプローチではないことを確認できればいいのですが...しかし、今のところ機能しています。

public string SplashText
{
    get
    {
        using (ThreadLock.Lock(_lock))
        {
            return _splashText;
        }
    }
    set
    {
        if (_dispatcher.CheckAccess())
        {
            _splashText = value;
            OnPropertyChanged();
            return;
        }
        _dispatcher.Invoke(() =>
        {
            _splashText = value;
            OnPropertyChanged();
        });
    }
}
于 2014-11-06T14:35:45.160 に答える
1

デッドロックの前提条件 (以前にデッドロックを見たことがない理由)

デッドロックが発生するために満たさなければならない 4 つの前提条件があります。それらの 1 つが欠けていても、デッドロックは発生しません。それらの前提条件は次のとおりです。

  • 相互排除
  • プリエンプションなし
  • 保留して待機
  • 循環待機

最後のものは「タイミング」と名付けることもできます。Windows がどのように CPU 時間を割り当てているかに依存するため、何年もデッドロックなしで生活できるかもしれません。2 つのスレッドが実際に並行して実行されている場合、循環待機が実現しやすいため、マルチコア CPU で発生する可能性が高くなります。

シンボル (SOSEX を読み込めない理由)

mscordacwks_AMD64_x86_4.0.30319.34209.dllは存在しないファイルです。WinDbg が探しているのを見たので、別のファイルの名前をそのファイル名に変更したことを認めてください。

この名前は、32 ビット アプリケーションを 64 ビット デバッガでデバッグしようとしていることを示しています。Microsoft はこれをサポートしていません。64 ビット WinDbg の 64 ビット .NET アプリケーションと、32 ビット WinDbg の 32 ビット .NET アプリケーションのみをデバッグできます (64 bis OS BTW でも実行されます)。

64 ビットのダンプ ファイルしかなく、問題を再現できない場合は、運が悪いです。それを機能させる方法はなく (何度か調査しました)、ダンプを 64 ビットから 32 ビットに変換する方法もありません。

問題を解決する

それ以外は、SOSEX' を使用するアプローチ!dlkが優れています。lockC#ステートメントによって引き起こされたデッドロックを検出する必要があります。

Jakob Christensenの回答で提案されているように、コードを同期的に実行することに同意しません。小さなアプリケーションではそれを行うこともできますが、大規模なアプリケーションでは書き直しが多すぎます。

同期実行に切り替えて非同期に戻すと、再び未検出の状況が発生する可能性があります (タイミングが変更され、デッドロックが発生する可能性が低くなっただけです)。

IMHOデッドロックを本当に理解することをお勧めします(Windowsの内部をある程度理解する必要があるため、本を読むことをお勧めします)。asyncWindows スレッディングを理解すると、 とについても理解が深まりますawait

それから私は言ったピーター・ダニホに同意します:

GUIスレッドの_splashTextフィールドにのみアクセスする場合-つまり、そのスレッドでWPFによって直接呼び出されるコード、またはそのスレッドに明示的にディスパッチしたコードで-その場合、はい...他は必要ありませんそのフィールドへのすべてのアクセスは、その単一のスレッドで同期的に行われるためです。

「GUIスレッド」だけではないことに注意してください。複数の UI スレッド、つまり独自のメッセージ キューを持つスレッドを作成する開発者がますます増えています。1つだけ持っていることを願っています。

于 2014-11-06T21:30:59.520 に答える
1

最初のステップは、タスクをまったく使用せずにコードを同期的に実行することです。

2 番目のステップは、正しく待機しているかどうかを確認することです。たとえば、 への呼び出しで await がありませんthis._addinSystem.InitializeAddIns()。これは、 を呼び出すInitializeAddIns前に への呼び出しが終了しない可能性があることを意味しますRunAddIns。これを追加する必要があります:

await this._addinSystem.InitializeAddIns();

最後に、呼び出しコードを正しく待機していない可能性があります。たとえば、void返される関数を待機している場合、呼び出しがデッドロックする可能性があります。

// This may deadlock because you are awaiting a void returning function!
await MyMethod();

//...

public void MyMethod()
{
    await this._addinSystem.InitializeAddIns();
    await Task.Run(() => this._addinSystem.RunAddins());    
}
于 2014-11-06T14:16:18.583 に答える