2013年4月11日木曜日

wpf : Dispatchder.BeginInvokeとlockステートメントを組み合わせるとデッドロックするらしい

Stack Overflowを見るとこんなやり取りがありました。

英語が読めないので斜め読みしかしてないですが、「lockステートメントの中でDispatchder.BeginInvokeするのは良くない」というお話のようです。 「you would deadlock yourself」などというレスが付いています。 こんなコードはダメ。

lock(lockObj)
{
    Dispatcher.BeginInvoke((Action)(() =>
    {
        処理;
    }));
}

どのスレッドとどのスレッドがデッドロックするのかは分かりづらいですね。 Dispatcher.BeginInvokeのところでスレッドの切り替わりとかがあるんでしょうか? 複数のスレッドで同時にこのコードが走ったらDispatcher.BeginInvokeのところでデッドロックするとか?

実際にデッドロックしたところを確認してないんでよく分からないんですが、とりあえずこういう処理をしたかったら自分でMonitorを使うしかないようです。 試しに書いてみたコードはこんな感じ。

bool lockTaken = false;
try
{
    Monitor.Enter(lockObj, ref lockTaken);

    Dispatcher.BeginInvoke((Action)(() =>
    {
        if (lockTaken)
        {
            処理;
            Monitor.Exit(lockObj);
        }
    }));
}
catch (Exception exc)
{
    if (lockTaken)
    {
        Monitor.Exit(lockObj);
    }
}

自分用の小さなコードで動かしてみたらちゃんと動いたけど、そもそもデッドロックが発生するような複雑なコードではないので検証不十分です。 あってるのかな、これ?

あってたとして、Actionが終わるまでロックしっぱなしなので使い方には注意です。 ちゃんとしたソフトで使う場合は長時間のロックが発生したときの処理も書かないとならないですね。