ロックステートメントの本体内で「await」演算子を使用できないのはなぜですか? 質問する

ロックステートメントの本体内で「await」演算子を使用できないのはなぜですか? 質問する

C# (.NET Async CTP) のキーワードはステートメントawait内では許可されませんlock

からマイクロソフト:

await 式は、同期関数、クエリ式、例外処理ステートメントの catch または finally ブロック、ロック ステートメントのブロック、または unsafe コンテキストでは使用できません。

何らかの理由で、コンパイラ チームにとってこれを実装するのは困難または不可能であると思われます。

私は 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();
}

Monitor.Exitしかし、これは期待どおりには動作しません。withinの呼び出しは、他のスレッドがロックを取得しようとすると、無期限にブロックされるようで (ほとんどの場合)、デッドロックが発生します。私の回避策の信頼性の低さと、ステートメントがステートメント内で許可されないExitDisposable.Dispose理由は、何らかの関連があると思われます。awaitlock

ステートメントの本文内で許可されない理由を 知っている人はいますか?awaitlock

ベストアンサー1

何らかの理由で、コンパイラ チームにとってこれを実装するのは困難または不可能であると思われます。

いいえ、実装するのはまったく困難でも不可能でもありません。自分で実装したという事実がその証拠です。むしろ、これは非常に悪いアイデアなので、この間違いを犯さないように保護するために許可していません。

ExitDisposable.Dispose 内での Monitor.Exit の呼び出しは、他のスレッドがロックを取得しようとするときにデッドロックを引き起こすため、無期限にブロックされるようです (ほとんどの場合)。私の回避策の信頼性の低さと、await ステートメントが lock ステートメントで許可されない理由は、何らかの関連があると思われます。

正解です。なぜこれを違法にしたのかお分かりいただけたと思います。ロック内で待機することはデッドロックを生み出す原因となります。

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

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

yield return同じ理由で、 a 内でa を実行することも「最悪のプラクティス」であることに注意してくださいlock。これを行うことは合法ですが、違法にすればよかったと思います。「await」で同じ間違いを犯すつもりはありません。

おすすめ記事