Das Schlüsselwort await in C # (.NET Async CTP) ist in einer lock-Anweisung nicht zulässig.
Von MSDN :
Ein Warten-Ausdruck kann nicht in einer synchronen Funktion, in einem Abfrageausdruck, im catch- oder finally-Block einer Ausnahmebehandlungsanweisung, im Block einer lock-Anweisung oder in einem unsicheren Kontext verwendet werden.
Ich gehe davon aus, dass dies für das Compilerteam aus irgendeinem Grund entweder schwierig oder unmöglich zu implementieren ist.
Ich habe versucht, die using-Anweisung zu umgehen:
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 in ExitDisposable.Dispose scheint auf unbestimmte Zeit (meistens) zu blockieren, was zu Deadlocks führt, wenn andere Threads versuchen, die Sperre zu erlangen. Ich vermute, dass die Unzuverlässigkeit meiner Arbeit und der Grund, warum Warten-Anweisungen in Lock-Anweisungen nicht zulässig sind, irgendwie zusammenhängen.
Weiß jemand, warum das Warten im Text einer Lock-Anweisung nicht erlaubt ist?
quelle
Antworten:
Nein, die Implementierung ist überhaupt nicht schwierig oder unmöglich - die Tatsache, dass Sie sie selbst implementiert haben, ist ein Beweis dafür. Vielmehr ist es eine unglaublich schlechte Idee , und so haben wir es nicht zulassen, so wie Sie zu schützen , diesen Fehler zu machen.
Richtig, Sie haben herausgefunden, warum wir es illegal gemacht haben. In einem Schloss wartet ein Rezept für die Herstellung von Deadlocks.
Ich bin sicher, Sie können verstehen, warum: Zwischen dem Zeitpunkt, an dem das Warten die Steuerung an den Aufrufer zurückgibt, und der Wiederaufnahme der Methode wird beliebiger Code ausgeführt . Dieser willkürliche Code könnte Sperren entfernen, die Inversionen der Sperrenreihenfolge und damit Deadlocks erzeugen.
Schlimmer noch, der Code könnte in einem anderen Thread fortgesetzt werden (in fortgeschrittenen Szenarien; normalerweise wird der Thread, der gewartet hat, aber nicht unbedingt wieder aufgenommen). In diesem Fall würde das Entsperren eine Sperre für einen anderen Thread als den Thread entsperren, der benötigt wurde aus dem Schloss. Ist das eine gute Idee? Nein.
Ich stelle fest, dass es aus dem gleichen Grund auch eine "schlimmste Praxis" ist, ein
yield return
Inside a zu machenlock
. Es ist legal, dies zu tun, aber ich wünschte, wir hätten es illegal gemacht. Wir werden nicht den gleichen Fehler für "Warten" machen.quelle
SemaphoreSlim.WaitAsync
Klasse dem .NET Framework hinzugefügt wurde, nachdem Sie diese Antwort veröffentlicht haben, können wir davon ausgehen, dass dies jetzt möglich ist. Unabhängig davon sind Ihre Kommentare zu den Schwierigkeiten bei der Implementierung eines solchen Konstrukts weiterhin uneingeschränkt gültig.Verwenden Sie die
SemaphoreSlim.WaitAsync
Methode.quelle
Stuff
sehe ich keinen Weg daran vorbei ...mySemaphoreSlim = new SemaphoreSlim(1, 1)
es funktioniertlock(...)
?Grundsätzlich wäre es das Falsche.
Dies kann auf zwei Arten implementiert 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 minimal halten Zeit halten. Dies ist möglicherweise auch unmöglich, da ein Thread eine Sperre und keine Methode besitzt - und Sie möglicherweise nicht einmal den Rest der asynchronen Methode auf demselben Thread ausführen (abhängig vom Taskplaner).
Lassen Sie die Sperre in der Wartezeit los und fordern Sie sie erneut an, wenn die Wartezeit zurückkehrt
Dies verstößt gegen das Prinzip der IMO mit dem geringsten Erstaunen, bei dem sich die asynchrone Methode so genau wie möglich wie der entsprechende synchrone Code verhalten sollte - es sei denn, Sie verwenden sie
Monitor.Wait
in einem Sperrblock Besitzen Sie die Sperre für die Dauer des Blocks.Grundsätzlich gibt es hier zwei konkurrierende Anforderungen - das sollten Sie nicht sein versuchen , die erste zu tun, und wenn Sie den zweiten Ansatz wählen möchten, können Sie den Code viel klarer machen, indem Sie zwei getrennte Sperrblöcke durch den Ausdruck wait trennen:
Indem die Sprache Ihnen verbietet, im Sperrblock selbst zu warten, zwingt Sie die Sprache dazu, darüber nachzudenken, was Sie wirklich tun möchten, und macht diese Auswahl in dem Code, den Sie schreiben, klarer.
quelle
SemaphoreSlim.WaitAsync
Klasse dem .NET Framework hinzugefügt wurde, nachdem Sie diese Antwort veröffentlicht haben, können wir davon ausgehen, dass dies jetzt möglich ist. Unabhängig davon sind Ihre Kommentare zu den Schwierigkeiten bei der Implementierung eines solchen Konstrukts weiterhin uneingeschränkt gültig.Dies ist nur eine Erweiterung dieser Antwort .
Verwendungszweck:
quelle
try
Blocks zu erhalten - wenn eine Ausnahme zwischen auftrittWaitAsync
undtry
das Semaphor niemals freigegeben wird (Deadlock). Auf der anderen Seite führt das Verschieben einesWaitAsync
Anrufs in dentry
Block zu einem weiteren Problem, wenn das Semaphor freigegeben werden kann, ohne dass eine Sperre erworben wird. Siehe den zugehörigen Thread, in dem dieses Problem erklärt wurde: stackoverflow.com/a/61806749/7889645Dies bezieht sich auf http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx , http://winrtstoragehelper.codeplex.com/ , Windows 8 App Store und .net 4.5
Hier ist mein Blickwinkel dazu:
Die Sprachfunktion async / await macht viele Dinge ziemlich einfach, führt aber auch ein Szenario ein, das selten vor der Verwendung von asynchronen Aufrufen aufgetreten ist: Wiedereintritt.
Dies gilt insbesondere für Ereignishandler, da Sie für viele Ereignisse keine Ahnung haben, was passiert, nachdem Sie vom Ereignishandler zurückgekehrt sind. Eine Sache, die tatsächlich passieren könnte, ist, dass die asynchrone Methode, auf die Sie im ersten Ereignishandler warten, von einem anderen Ereignishandler aufgerufen wird, der sich noch im selben Thread befindet.
Hier ist ein reales Szenario, auf das ich in einer Windows 8 App Store-App gestoßen bin: Meine App hat zwei Frames: Ein- und Aussteigen aus einem Frame, in den ich einige Daten in eine Datei / einen Speicher laden / sichern möchte. OnNavigatedTo / From-Ereignisse werden zum Speichern und Laden verwendet. Das Speichern und Laden erfolgt über eine asynchrone Dienstprogrammfunktion (wie http://winrtstoragehelper.codeplex.com/ ). Beim Navigieren von Frame 1 zu Frame 2 oder in die andere Richtung werden die asynchrone Last und die sicheren Operationen aufgerufen und abgewartet. Die Event-Handler werden asynchron und geben void zurück => Sie können nicht erwartet werden.
Die erste Dateiöffnungsoperation (sagen wir: innerhalb einer Speicherfunktion) des Dienstprogramms ist jedoch ebenfalls asynchron, und daher gibt die erste Wartezeit die Steuerung an das Framework zurück, das einige Zeit später das andere Dienstprogramm (Laden) über den zweiten Ereignishandler aufruft. Das Laden versucht nun, dieselbe Datei zu öffnen. Wenn die Datei jetzt für den Speichervorgang geöffnet ist, schlägt dies mit einer ACCESSDENIED-Ausnahme fehl.
Eine Mindestlösung für mich besteht darin, den Dateizugriff über ein using und ein AsyncLock zu sichern.
Bitte beachten Sie, dass seine Sperre grundsätzlich alle Dateivorgänge für das Dienstprogramm mit nur einer Sperre sperrt, was unnötig stark ist, aber für mein Szenario gut funktioniert.
Hier ist mein Testprojekt: Eine Windows 8 App Store-App mit einigen Testaufrufen für die Originalversion von http://winrtstoragehelper.codeplex.com/ und meine modifizierte Version, die AsyncLock von Stephen Toub http: //blogs.msdn verwendet. com / b / pfxteam / archive / 2012/02/12/10266988.aspx .
Darf ich auch diesen Link vorschlagen: http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx
quelle
Stephen Taub hat eine Lösung für diese Frage implementiert, siehe Erstellen von Grundelementen für die asynchrone Koordination, Teil 7: AsyncReaderWriterLock .
Stephen Taub genießt in der Branche hohes Ansehen, daher ist alles, was er schreibt, wahrscheinlich solide.
Ich werde den Code, den er in seinem Blog gepostet hat, nicht reproduzieren, aber ich werde Ihnen zeigen, wie man ihn verwendet:
Wenn Sie eine Methode möchten, die in das .NET Framework integriert ist, verwenden Sie
SemaphoreSlim.WaitAsync
stattdessen. Sie erhalten keine Lese- / Schreibsperre, aber Sie erhalten eine bewährte Implementierung.quelle
SemaphoreSlim.WaitAsync
es im .NET Framework der Fall ist. Dieser Code fügt lediglich ein Lese- / Schreibsperrkonzept hinzu.Hmm, sieht hässlich aus, scheint zu funktionieren.
quelle
Ich habe versucht, einen Monitor (Code unten) zu verwenden, der zu funktionieren scheint, aber ein GOTCHA hat ... wenn Sie mehrere Threads haben, wird es ... System.Threading.SynchronizationLockException Die Objektsynchronisationsmethode wurde aus einem nicht synchronisierten Codeblock aufgerufen.
Vorher habe ich das einfach gemacht, aber es war in einem ASP.NET-Controller, so dass es zu einem Deadlock kam.
public async Task<FooResponse> ModifyFooAsync() { lock(lockObject) { return SomeFunctionToModifyFooAsync.Result; } }
quelle