527 Stimmen

Warum kann ich den 'await'-Operator nicht innerhalb des Körpers einer Sperranweisung verwenden?

Das Schlüsselwort await in C# (.NET Async CTP) ist innerhalb einer Sperranweisung nicht zulässig.

Desde MSDN :

Eine await-Ausdruck kann nicht verwendet werden in einer synchronen Funktion, in einer Abfrage Ausdruck, im catch- oder finally-Block einer Ausnahmebehandlung Anweisung, im Block einer Sperranweisung oder in einem unsicheren Kontext.

Ich nehme an, dass dies für das Compiler-Team entweder schwierig oder aus irgendeinem Grund unmöglich zu implementieren ist.

Ich habe versucht, mit der using-Anweisung eine Lösung zu finden:

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

Dies funktioniert jedoch nicht wie erwartet. Der Aufruf von Monitor.Exit innerhalb von ExitDisposable.Dispose scheint auf unbestimmte Zeit zu blockieren (die meiste Zeit), was zu Deadlocks führt, da andere Threads versuchen, die Sperre zu übernehmen. Ich vermute, dass die Unzuverlässigkeit meines Workarounds und der Grund, warum await-Anweisungen in Lock-Anweisungen nicht zulässig sind, irgendwie zusammenhängen.

Weiß jemand por qué await ist innerhalb des Körpers einer Lock-Anweisung nicht erlaubt?

43 Stimmen

Ich könnte mir vorstellen, dass Sie den Grund gefunden haben, warum das nicht erlaubt ist.

3 Stimmen

Darf ich diesen Link empfehlen? hanselman.com/blog/ und diese hier: blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx

0 Stimmen

Ich fange gerade an, aufzuholen und lernen ein wenig mehr über async Programmierung. Nach zahlreichen Deadlocks in meinem wpf-Anwendungen, fand ich diesen Artikel zu einem großen sicheren Schutz in async Programmierung Praktiken sein. msdn.microsoft.com/de-us/magazine/

519voto

Eric Lippert Punkte 628543

Ich nehme an, dass dies für das Compiler-Team entweder schwierig oder aus irgendeinem Grund unmöglich zu implementieren ist.

Nein, es ist keineswegs schwierig oder unmöglich, es umzusetzen - die Tatsache, dass Sie es selbst umgesetzt haben, ist ein Beweis dafür. Im Gegenteil, es ist eine unglaublich schlechte Idee und deshalb lassen wir das nicht zu, um Sie vor diesem Fehler zu schützen.

Aufruf von Monitor.Exit innerhalb von ExitDisposable.Dispose scheint auf unbestimmte Zeit zu blockieren (die meiste Zeit), was zu Deadlocks führt, da andere Threads versuchen, die Sperre zu erhalten. Ich vermute, dass die Unzuverlässigkeit meines Workarounds und der Grund, warum await-Anweisungen in Lock-Anweisungen nicht zulässig sind, irgendwie zusammenhängen.

Richtig, Sie haben entdeckt, warum wir es illegal gemacht haben. Das Warten innerhalb einer Sperre ist ein Rezept für die Erzeugung von Deadlocks.

Ich bin sicher, Sie können verstehen, warum: Beliebiger Code läuft zwischen dem Zeitpunkt, an dem await die Kontrolle an den Aufrufer zurückgibt, und der Wiederaufnahme der Methode . Dieser willkürliche Code könnte Sperren entfernen, die zu Umkehrungen der Sperrreihenfolge und damit zu Deadlocks führen.

Schlimmer, der Code könnte in einem anderen Thread fortgesetzt werden (in fortgeschrittenen Szenarien; normalerweise nimmt man den Thread wieder auf, der die Sperre aufgehoben hat, aber nicht notwendigerweise); in diesem Fall würde die Entsperrung eine Sperre auf einem anderen Thread aufheben als dem Thread, der die Sperre aufgehoben hat. Ist das eine gute Idee? Nein.

Ich stelle fest, dass es auch eine "schlechte Praxis" ist, eine yield return innerhalb einer lock aus demselben Grund. Es ist legal, dies zu tun, aber ich wünschte, wir hätten es illegal gemacht. Wir werden nicht den gleichen Fehler machen, um "abzuwarten".

463voto

user1639030 Punkte 4059

Utilice SemaphoreSlim.WaitAsync Methode.

 await mySemaphoreSlim.WaitAsync();
 try {
     await Stuff();
 } finally {
     mySemaphoreSlim.Release();
 }

96voto

Sergei Punkte 5385

Dies ist nur eine Erweiterung von diese Antwort .

using System;
using System.Threading;
using System.Threading.Tasks;

public class SemaphoreLocker
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

    public async Task LockAsync(Func<Task> worker)
    {
        await _semaphore.WaitAsync();
        try
        {
            await worker();
        }
        finally
        {
            _semaphore.Release();
        }
    }

    // overloading variant for non-void methods with return type (generic T)
    public async Task<T> LockAsync<T>(Func<Task<T>> worker)
    {
        await _semaphore.WaitAsync();
        try
        {
            return await worker();
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

Verwendung:

public class Test
{
    private static readonly SemaphoreLocker _locker = new SemaphoreLocker();

    public async Task DoTest()
    {
        await _locker.LockAsync(async () =>
        {
            // [async] calls can be used within this block 
            // to handle a resource by one thread. 
        });
        // OR
        var result = await _locker.LockAsync(async () =>
        {
            // [async] calls can be used within this block 
            // to handle a resource by one thread. 
        });
    }
}

82voto

Jon Skeet Punkte 1325502

Im Grunde genommen wäre es das Falsche zu tun.

Dazu gibt es zwei Möglichkeiten könnte umgesetzt werden:

  • Halten Sie das Schloss fest und lassen Sie es erst am Ende des Blocks los. .
    Dies ist eine wirklich schlechte Idee, da Sie nicht wissen, wie lange der asynchrone Vorgang dauern wird. Sie sollten Sperren nur halten für minimal Zeitspannen. Es ist auch potentiell unmöglich, da ein Gewinde besitzt eine Sperre, keine Methode - und Sie können nicht einmal den Rest der asynchronen Methode auf demselben Thread ausführen (abhängig vom Taskplaner).

  • Freigabe der Sperre in der Wartezeit und Wiedererlangung der Sperre, wenn die Wartezeit zurückkehrt
    Dies verstößt gegen das Prinzip der geringsten Verwunderung IMO, wo die asynchrone Methode so nah wie möglich wie der entsprechende synchrone Code verhalten sollte - es sei denn, Sie verwenden Monitor.Wait in einem Sperrblock, erwarten Sie, dass Sie die Sperre für die Dauer des Blocks besitzen.

Im Grunde gibt es hier also zwei konkurrierende Anforderungen - Sie sollten nicht Versuch Wenn Sie sich für den zweiten Ansatz entscheiden, können Sie den Code durch zwei getrennte Sperrblöcke, die durch den await-Ausdruck getrennt sind, wesentlich übersichtlicher gestalten:

// Now it's clear where the locks will be acquired and released
lock (foo)
{
}
var result = await something;
lock (foo)
{
}

Indem die Sprache Ihnen also verbietet, im Sperrblock selbst zu warten, zwingt sie Sie dazu, darüber nachzudenken, was Sie wirklich und machen Sie diese Entscheidung in Ihrem Code deutlicher.

13voto

Contango Punkte 70203

Stephen Taub hat eine Lösung für diese Frage gefunden, siehe Erstellung von Primitiven zur Asynchronitätskoordination, Teil 7: AsyncReaderWriterLock .

Stephen Taub ist in der Branche hoch angesehen, so dass alles, was er schreibt, wahrscheinlich solide sein wird.

Ich werde den Code, den er in seinem Blog veröffentlicht hat, nicht reproduzieren, aber ich werde Ihnen zeigen, wie Sie ihn verwenden können:

/// <summary>
///     Demo class for reader/writer lock that supports async/await.
///     For source, see Stephen Taub's brilliant article, "Building Async Coordination
///     Primitives, Part 7: AsyncReaderWriterLock".
/// </summary>
public class AsyncReaderWriterLockDemo
{
    private readonly IAsyncReaderWriterLock _lock = new AsyncReaderWriterLock(); 

    public async void DemoCode()
    {           
        using(var releaser = await _lock.ReaderLockAsync()) 
        { 
            // Insert reads here.
            // Multiple readers can access the lock simultaneously.
        }

        using (var releaser = await _lock.WriterLockAsync())
        {
            // Insert writes here.
            // If a writer is in progress, then readers are blocked.
        }
    }
}

Wenn Sie eine Methode wünschen, die in das .NET-Framework integriert ist, verwenden Sie SemaphoreSlim.WaitAsync stattdessen. Sie erhalten keine Lese-/Schreibsperre, aber eine bewährte und getestete Implementierung.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X