1. ホーム
  2. c#

[解決済み] ロックステートメントのボディ内で 'await' 演算子を使用できないのはなぜですか?

2022-03-23 03:06:27

質問

C# (.NET Async CTP)のawaitキーワードは、lock文の中からは使用できない。

から MSDN :

を使用します。 await 式は使用できません。 同期関数、クエリ 例外処理のキャッチブロックやファイナルブロックの中にある ステートメントを使用します。 ロックステートメントのブロック内 または安全でないコンテキストで

これは、コンパイラーチームが何らかの理由で実装するのが難しいか不可能なのだと思います。

using文で回避策を試みました。

class Async
{
    public static async Task<IDisposable> Lock(object obj)
    {
        while (!Monitor.TryEnter(obj))
            await TaskEx.Yield();

        return new ExitDisposable(obj);
    }

    private class ExitDisposable : IDisposable
    {
        private readonly object obj;
        public ExitDisposable(object obj) { this.obj = obj; }
        public void Dispose() { Monitor.Exit(this.obj); }
    }
}

// example usage
using (await Async.Lock(padlock))
{
    await SomethingAsync();
}

しかし、これは期待通りに動作しません。 ExitDisposable.Dispose 内の Monitor.Exit の呼び出しは、他のスレッドがロックを取得しようとすると、(ほとんどの場合)無期限にブロックされてデッドロックを引き起こすようです。 私は、私の回避策の信頼性の低さと、await文がlock文の中で許可されていない理由が、何らかの形で関連していると考えています。

どなたかご存知ですか? なぜ await は lock 文の本文中では使用できないのですか?

解決方法は?

<ブロッククオート

これは、コンパイラチームが何らかの理由で実装するのが難しいか不可能なのだと思います。

いいえ、実装が難しいとか不可能ということは全くありません。あなたが自分で実装したことがその証です。むしろ、そうです。 それは信じられないほど悪い考えです そのため、このような失敗をしないように、許可していないのです。

ExitDisposable.Dispose 内の Monitor.Exit の呼び出しは、他のスレッドがロックを取得しようとするため、(ほとんどの場合)無期限にブロックされてデッドロックが発生するようです。私は、私の回避策の信頼性の低さと、await 文が lock 文で許可されない理由が、何らかの形で関連していると考えています。

正解です!なぜ違法としたのかがわかりましたね。 ロックの内側で待機することは、デッドロックを発生させるレシピです。

その理由はおわかりいただけたかと思います。 await が呼び出し元に制御を返してからメソッドが再開するまでの間に、任意のコードが実行されます。 . その任意のコードは、ロックの順序を逆転させ、デッドロックを発生させるロックを取り出している可能性があります。

さらに悪いことに コードが別のスレッドで再開される可能性がある (高度なシナリオでは、通常は await を実行したスレッドで再び拾いますが、そうとは限りません) その場合、ロックを解除すると、ロックを取り出したスレッドとは別のスレッドでロックを解除することになります。これは良いアイデアでしょうか?いいえ。

を行うことも"ワーストプラクティス"であることに注意してください。 yield return の中に lock というのは、同じ理由です。そうすることは合法ですが、違法にすればよかったのにと思います。私たちは、"await"のために同じ過ちを犯すつもりはありません。