Le mot clé wait en C # (.NET Async CTP) n'est pas autorisé à partir d'une instruction de verrouillage.
Depuis 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 lock ou dans un contexte non sécurisé.
Je suppose que cela est difficile ou impossible pour l'équipe de compilation à implémenter pour une raison quelconque.
J'ai tenté une solution de contournement avec l'instruction 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();
}
Cependant, cela ne fonctionne pas comme prévu. L'appel à Monitor.Exit dans ExitDisposable.Dispose semble se bloquer indéfiniment (la plupart du temps) provoquant des blocages lorsque d'autres threads tentent d'acquérir le verrou. Je soupçonne le manque de fiabilité de mon travail et la raison pour laquelle les déclarations d'attente ne sont pas autorisées dans la déclaration de verrouillage sont en quelque sorte liées.
Quelqu'un sait-il pourquoi l' attente n'est pas autorisée dans le corps d'une instruction de verrouillage?
la source
Réponses:
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 idée incroyablement mauvaise et nous ne la permettons pas, afin de vous protéger contre cette erreur.
C'est exact, 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 blocages.
Je suis sûr que vous pouvez voir pourquoi: du code arbitraire s'exécute entre le moment où le contrôle de retour en attente à l'appelant et la méthode reprend . Ce code arbitraire pourrait supprimer des verrous qui produisent des inversions d'ordre de verrouillage, et donc des blocages.
Pire, le code pourrait reprendre sur un autre thread (dans les scénarios avancés; normalement, vous reprenez sur le thread qui a attendu, mais pas nécessairement), auquel cas le déverrouillage déverrouillerait un verrou sur un thread différent de celui qui a pris la serrure. est-ce une bonne idée? Non.
Je note que c'est aussi une "pire pratique" de faire un
yield return
intérieur d'unlock
, pour la même raison. Il est légal de le faire, mais j'aurais souhaité que nous l'ayons rendu illégal. Nous n'allons pas faire la même erreur pour "attendre".la source
SemaphoreSlim.WaitAsync
classe a été ajoutée au framework .NET bien après la publication de cette réponse, je pense que nous pouvons supposer en toute sécurité que c'est possible maintenant. Quoi qu'il en soit, vos commentaires sur la difficulté de mettre en œuvre une telle construction sont toujours entièrement valables.Utilisez la
SemaphoreSlim.WaitAsync
méthode.la source
Stuff
je ne vois aucun moyen de le contourner ...mySemaphoreSlim = new SemaphoreSlim(1, 1)
pour fonctionner commelock(...)
?Ce serait fondamentalement la mauvaise chose à faire.
Il y a deux façons cela pourrait être mis en œuvre:
Maintenez la serrure, ne la relâchez qu'à la fin du bloc .
C'est une très mauvaise idée car vous ne savez pas combien de temps l'opération asynchrone va prendre. Vous ne devez tenir les verrous que pendant une durée minimale . 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 reste de la méthode asynchrone sur le même thread (selon le planificateur de tâches).
Relâchez le verrou dans l'attente et réacquérez-le lorsque l'attente revient
Cela viole le principe de l'OMI le moins étonnant, où la méthode asynchrone doit se comporter aussi étroitement que possible comme le code synchrone équivalent - sauf si vous utilisez
Monitor.Wait
dans un bloc de verrouillage, vous vous attendez à posséder la serrure pour la durée du bloc.Donc, fondamentalement, il y a deux exigences concurrentes ici - vous ne devriez pas essayer de faire la première ici, et si vous voulez adopter la deuxième approche, vous pouvez rendre le code beaucoup plus clair en ayant deux blocs de verrouillage séparés séparés par l'expression d'attente:
Ainsi, en vous interdisant d'attendre dans le bloc de verrouillage 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.
la source
SemaphoreSlim.WaitAsync
classe a été ajoutée au framework .NET bien après la publication de cette réponse, je pense que nous pouvons supposer en toute sécurité que c'est possible maintenant. Quoi qu'il en soit, vos commentaires sur la difficulté de mettre en œuvre une telle construction sont toujours entièrement valables.Ce n'est qu'une extension de cette réponse .
Usage:
la source
try
bloc - si une exception se produit entreWaitAsync
et quetry
le sémaphore ne sera jamais libéré (blocage). D'un autre côté, le déplacement de l'WaitAsync
appel dans letry
bloc introduira un autre problème, lorsque le sémaphore peut être libéré sans qu'un verrou ne soit acquis. Voir le fil associé où ce problème a été expliqué: stackoverflow.com/a/61806749/7889645Cela fait référence à 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 point de vue à ce sujet:
La fonctionnalité de langue asynchrone / attente rend beaucoup de choses assez faciles, mais elle introduit également un scénario qui était rarement rencontré avant qu'il ne soit si facile d'utiliser des appels asynchrones: la 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 réellement se produire est que la méthode asynchrone que vous attendez dans le premier gestionnaire d'événements est appelée à partir d'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 cadres: entrant et sortant d'un cadre, je veux charger / sauvegarder certaines données dans un fichier / stockage. Les événements OnNavigatedTo / From sont utilisés pour l'enregistrement et le chargement. L'enregistrement et le chargement sont effectués par une fonction utilitaire asynchrone (comme http://winrtstoragehelper.codeplex.com/ ). Lorsque vous naviguez de la trame 1 à la trame 2 ou dans l'autre sens, la charge asynchrone et les opérations sûres 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 de fichier (disons: à l'intérieur d'une fonction de sauvegarde) de l'utilitaire est également asynchrone et donc la première attente retourne le contrôle sur le framework, qui appellera parfois l'autre utilitaire (charge) via le deuxième gestionnaire d'événements. Le chargement essaie maintenant d'ouvrir le même fichier et si le fichier est désormais ouvert pour l'opération de sauvegarde, échoue avec une exception ACCESSDENIED.
Une solution minimale pour moi est de sécuriser l'accès au fichier via un using et un AsyncLock.
Veuillez noter que son verrou verrouille essentiellement toutes les opérations sur les fichiers pour l'utilitaire avec un seul verrou, ce qui est inutilement fort mais fonctionne bien 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 le 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
la source
Stephen Taub a implémenté une solution à cette question, voir Création de primitives de coordination asynchrone, partie 7: AsyncReaderWriterLock .
Stephen Taub est très apprécié dans l'industrie, 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 vais vous montrer comment l'utiliser:
Si vous voulez une méthode intégrée au framework .NET, utilisez
SemaphoreSlim.WaitAsync
plutôt. Vous n'obtiendrez pas de verrou de lecture / écriture, mais vous obtiendrez une implémentation éprouvée.la source
SemaphoreSlim.WaitAsync
c'est le cas dans le cadre .NET. Tout ce code ne fait qu'ajouter un concept de verrouillage de lecteur / écrivain.Hmm, l'air moche, semble fonctionner.
la source
J'ai essayé d'utiliser un moniteur (code ci-dessous) qui semble fonctionner mais qui a un GOTCHA ... lorsque vous avez plusieurs threads, il donnera ... System.Threading.SynchronizationLockException La méthode de synchronisation d'objet a été appelée à partir d'un bloc de code non synchronisé.
Avant cela, je faisais simplement cela, mais c'était dans un contrôleur ASP.NET, ce qui a entraîné un blocage.
public async Task<FooResponse> ModifyFooAsync() { lock(lockObject) { return SomeFunctionToModifyFooAsync.Result; } }
la source