Le mot-clé wait en C # (.NET Async CTP) n’est pas autorisé dans une instruction de locking.
De MSDN :
Une expression d’attente ne peut pas être utilisée dans une fonction synchrone, dans une expression de requête, dans le bloc catch ou finally d’une instruction de gestion des exceptions, dans le bloc d’une instruction de locking ou dans un contexte dangereux.
Je suppose que c’est difficile ou impossible pour l’équipe du compilateur à implémenter pour une raison quelconque.
J’ai tenté un travail autour de la déclaration using:
class Async { public static async Task 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(); }
Cependant, cela ne fonctionne pas comme prévu. L’appel de Monitor.Exit dans ExitDisposable.Dispose semble bloquer indéfiniment (la plupart du temps), provoquant des blocages lorsque d’autres threads tentent d’acquérir le verrou. Je soupçonne que le manque de fiabilité de mon travail et la raison pour laquelle les instructions en attente ne sont pas autorisées dans une instruction de locking sont en quelque sorte liées.
Est-ce que quelqu’un sait pourquoi attendre n’est pas autorisé dans le corps d’une déclaration de locking?
Je suppose que c’est difficile ou impossible pour l’équipe du compilateur à implémenter pour une raison quelconque.
Non, ce n’est pas du tout difficile ou impossible à mettre en œuvre – le fait que vous l’ayez mis en œuvre vous-même en témoigne. C’est plutôt une très mauvaise idée et nous ne le permettons pas, afin de vous protéger contre cette erreur.
appel à Monitor.Exit dans ExitDisposable.Dispose semble bloquer indéfiniment (la plupart du temps) provoquant des blocages lorsque d’autres threads tentent d’acquérir le verrou. Je soupçonne que le manque de fiabilité de mon travail et la raison pour laquelle les instructions en attente ne sont pas autorisées dans une instruction de locking sont en quelque sorte liées.
Correct, vous avez découvert pourquoi nous l’avons rendu illégal. L’attente à l’intérieur d’une serrure est une recette pour produire des impasses.
Je suis sûr que vous pouvez voir pourquoi: le code arbitraire s’exécute entre le moment où l’attente renvoie le contrôle à l’appelant et la méthode reprend . Ce code arbitraire pourrait prendre des verrous qui produisent des inversions d’ordre des verrous, et donc des blocages.
Pire encore, le code pourrait reprendre sur un autre thread (dans les scénarios avancés; normalement, vous reprenez sur le thread qui l’attendait, mais pas nécessairement), auquel cas le délocking déverrouillerait un verrou sur un thread différent du thread qui le prendait. sortir la serrure. est-ce une bonne idée? Non.
Je note que c’est aussi une “pire pratique” de faire un yield return
dans une lock
, pour la même raison. C’est légal de le faire, mais j’aimerais que nous l’ayons rendu illégal. Nous n’allons pas faire la même erreur pour “attendre”.
Utilisez la méthode SemaphoreSlim.WaitAsync
.
await mySemaphoreSlim.WaitAsync(); try { await Stuff(); } finally { mySemaphoreSlim.Release(); }
Fondamentalement, ce serait la mauvaise chose à faire.
Cela peut être mis en œuvre de deux manières:
Maintenez le verrou, ne le relâchez qu’à la fin du bloc .
C’est une très mauvaise idée car vous ne savez pas combien de temps va durer l’opération asynchrone. Vous ne devriez tenir les serrures que pour un minimum de temps. C’est également potentiellement impossible, car un thread possède un verrou, pas une méthode – et vous ne pouvez même pas exécuter le rest de la méthode asynchrone sur le même thread (en fonction du planificateur de tâches).
Relâchez le verrou dans l’attente et récupérez-le lorsque l’attente est de retour
Ceci viole le principe de moindre étonnement IMO, où la méthode asynchrone doit se comporter aussi étroitement que le code synchrone équivalent – à moins que vous n’utilisiez Monitor.Wait
dans un bloc de locking, vous vous attendez à posséder le verrou pour la durée du bloc.
Donc, fondamentalement, il y a deux exigences concurrentes ici – vous ne devriez pas essayer de faire le premier ici, et si vous voulez adopter la seconde approche, vous pouvez rendre le code plus clair en ayant deux blocs de locking séparés par une expression d’attente:
// Now it's clear where the locks will be acquired and released lock (foo) { } var result = await something; lock (foo) { }
Donc, en vous interdisant d’attendre dans le bloc de locking lui-même, le langage vous oblige à réfléchir à ce que vous voulez vraiment faire et à rendre ce choix plus clair dans le code que vous écrivez.
Cela se réfère à http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx , http://winrtstoragehelper.codeplex.com/ , Windows 8 app store et .net 4.5
Voici mon angle sur ceci:
La fonction de langage asynchrone / en attente rend beaucoup de choses assez faciles, mais elle introduit également un scénario qui était rarement rencontré avant qu’il soit si facile d’utiliser des appels asynchrones: réentrance.
Cela est particulièrement vrai pour les gestionnaires d’événements, car pour de nombreux événements, vous n’avez aucune idée de ce qui se passe après votre retour du gestionnaire d’événements. Une chose qui pourrait arriver est que la méthode asynchrone que vous attendez dans le premier gestionnaire d’événements est appelée par un autre gestionnaire d’événements toujours sur le même thread.
Voici un scénario réel que j’ai rencontré dans une application Windows 8 App Store: Mon application a deux frameworks: entrer et sortir d’un cadre Je veux charger / sécuriser des données dans un fichier / stockage. Les événements OnNavigatedTo / From sont utilisés pour la sauvegarde et le chargement. La sauvegarde et le chargement sont effectués par une fonction d’utilitaire asynchrone (comme http://winrtstoragehelper.codeplex.com/ ). Lorsque vous naviguez de l’image 1 à l’image 2 ou dans l’autre direction, les opérations de chargement asynchrone et de sécurité sont appelées et attendues. Les gestionnaires d’événements deviennent asynchrones et renvoient void => ils ne peuvent pas être attendus.
Cependant, la première opération d’ouverture du fichier (à savoir: dans une fonction de sauvegarde) de l’utilitaire est également asynchrone et la première attente renvoie le contrôle au framework, qui appelle ensuite l’autre utilitaire (load) via le second gestionnaire d’événements. Le chargement essaie maintenant d’ouvrir le même fichier et si le fichier est maintenant ouvert pour l’opération de sauvegarde, échoue avec une exception ACCESSDENIED.
Une solution minimale pour moi est de sécuriser l’access aux fichiers via un user et un AsyncLock.
private static readonly AsyncLock m_lock = new AsyncLock(); ... using (await m_lock.LockAsync()) { file = await folder.GetFileAsync(fileName); IRandomAccessStream readStream = await file.OpenAsync(FileAccessMode.Read); using (Stream inStream = Task.Run(() => readStream.AsStreamForRead()).Result) { return (T)serializer.Deserialize(inStream); } }
Veuillez noter que son verrou verrouille essentiellement toutes les opérations de fichiers pour l’utilitaire avec un seul verrou, ce qui est inutilement fort, mais fonctionne correctement pour mon scénario.
Voici mon projet de test: une application Windows 8 App Store avec quelques appels de test pour la version originale de http://winrtstoragehelper.codeplex.com/ et ma version modifiée qui utilise AsyncLock de Stephen Toub http: //blogs.msdn. com / b / pfxteam / archive / 2012/02/12 / 10266988.aspx .
Puis-je également suggérer ce lien: http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx
Ceci est juste une extension de cette réponse .
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 worker) { await _semaphore.WaitAsync(); try { await worker(); } finally { _semaphore.Release(); } } }
Usage:
public class Test { private static readonly SemaphoreLocker _locker = new SemaphoreLocker(); public async Task DoTest() { await _locker.LockAsync(async () => { // [asyn] calls can be used within this block // to handle a resource by one thread. }); } }
Hmm, semble moche, semble fonctionner.
static class Async { public static Task Lock(object obj) { return TaskEx.Run(() => { var resetEvent = ResetEventFor(obj); resetEvent.WaitOne(); resetEvent.Reset(); return new ExitDisposable(obj) as IDisposable; }); } private static readonly IDictionary
Stephen Taub a mis en place une solution à cette question, voir Construction de primitives de coordination asynchrone, Partie 7: AsyncReaderWriterLock .
Stephen Taub est très apprécié dans le secteur, donc tout ce qu’il écrit est susceptible d’être solide.
Je ne reproduirai pas le code qu’il a posté sur son blog, mais je montrerai comment l’utiliser:
/// /// 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". /// 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. } } }
Si vous utilisez une méthode intégrée au framework .NET, utilisez plutôt SemaphoreSlim.WaitAsync
. Vous n’obtiendrez pas de verrou lecteur / graveur, mais vous aurez une implémentation éprouvée.
J’ai essayé d’utiliser un moniteur (code ci-dessous) qui semble fonctionner mais qui a un GOTCHA … lorsque vous avez plusieurs threads … System.Threading.SynchronizationLockException La méthode de synchronisation des objects a été appelée à partir d’un bloc de code non synchronisé.
using System; using System.Threading; using System.Threading.Tasks; namespace MyNamespace { public class ThreadsafeFooModifier : { private readonly object _lockObject; public async Task ModifyFooAsync() { FooResponse result; Monitor.Enter(_lockObject); try { result = await SomeFunctionToModifyFooAsync(); } finally { Monitor.Exit(_lockObject); } return result; } } }
Avant cela, je faisais simplement cela, mais c’était dans un contrôleur ASP.NET, il en résultait un blocage.
public async Task