450 votes

Pourquoi ne ' t j’utilise les ' attendent ' opérateur dans le corps d’une instruction lock ?

Le mot clé await en C# (.NET Async CTP) n'est pas autorisé à partir à l'intérieur d'une instruction lock.

À partir de MSDN:

Un attendent expression ne peut pas être utilisé dans une fonction synchrone, dans une requête l'expression, dans le bloc catch ou finally d'un traitement d'exception déclaration, dans le bloc de verrouillage de l'instruction, ou dans un contexte dangereux.

Je suppose que c'est soit difficile, voire impossible, pour le compilateur de l'équipe à mettre en œuvre pour une raison quelconque.

J'ai tenté un travail autour avec l'aide de déclaration:

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

Toutefois, cela ne fonctionne pas comme prévu. L'appel à Surveiller.Sortie dans ExitDisposable.Jetez semble bloquer indéfiniment (la plupart du temps) provoquant des blocages que d'autres threads tentent d'acquérir le verrou. Je soupçonne que le manque de fiabilité de mon travail autour de et la raison pour attendre les instructions ne sont pas autorisés dans le verrouillage de la déclaration sont en quelque sorte liés.

Personne ne sait pourquoi attendre n'est pas autorisé dans le corps d'une instruction lock?

461voto

Eric Lippert Points 300275

Je suppose que c'est soit difficile, voire impossible, pour le compilateur de l'équipe à mettre en œuvre pour une raison quelconque.

Non, il n'est pas difficile ou impossible à mettre en œuvre -- le fait que vous avez mis en place vous-même est un témoignage de ce fait. Plutôt, il est d'une incroyable mauvaise idée, et si nous ne le permet pas, de manière à vous protéger de cette erreur.

appel à Surveiller.Sortie dans ExitDisposable.Jetez semble bloquer indéfiniment (la plupart du temps) provoquant des blocages que d'autres threads tentent d'acquérir le verrou. Je soupçonne que le manque de fiabilité de mon travail autour de et la raison pour attendre les instructions ne sont pas autorisés dans le verrouillage de la déclaration sont en quelque sorte liés.

Correct, vous avez découvert pourquoi nous l'avons fait illégale. L'attente à l'intérieur d'une serrure est une recette pour la production de blocages.

Je suis sûr que vous pouvez voir pourquoi: l'exécution de code arbitraire entre le moment où l'attendent retourne le contrôle à l'appelant et la méthode reprend. Que du code arbitraire à prendre de serrures qui produisent de verrouillage de la commande de l'inversion, et donc les blocages.

Pire, le code pourrait reprendre sur un autre thread (dans les scénarios avancés; normalement, vous ramasser à nouveau sur le fil qui ne l'attendent, mais pas nécessairement), auquel cas le déverrouiller serait débloquer un verrou sur un thread différent de celui qui a pris la serrure. Est-ce une bonne idée? Pas de.

Je note que c'est aussi une des pires pratiques" de faire un yield return à l'intérieur d'un lock, pour la même raison. Il est légal de le faire, mais je souhaite que nous avait fait que c'est illégal. Nous n'allons pas faire la même erreur pour "attendre".

387voto

user1639030 Points 131

Utilisation `` méthode.

77voto

Jon Skeet Points 692016

Fondamentalement, ce serait une mauvaise chose à faire.

Il y a deux façons, cela pourrait être mis en œuvre:

  • Garder la main sur la serrure, seulement de le libérer à la fin du bloc.
    C'est une très mauvaise idée que vous ne savez pas combien de temps l'opération asynchrone va prendre. Vous devez contenir uniquement des serrures pour les minimes quantités de temps. C'est aussi potentiellement impossible, comme un thread possède un verrou, pas une méthode - et vous pouvez même ne pas exécuter le reste de la méthode asynchrone sur le même thread (selon le planificateur de tâches).

  • Libérer le verrou dans l'attendent, et acquérir il lorsque l'attendent des retours
    Cela viole le principe de moindre étonnement de l'OMI, où la méthode asynchrone doit se comporter comme étroitement que possible, comme l'équivalent du code synchrone - sauf si vous utilisez Monitor.Wait dans une serrure de bloc, vous vous attendez à posséder la serrure pour la durée du bloc.

Donc, fondamentalement, il existe deux exigences contradictoires ici - vous ne devriez pas essayer de faire le premier ici, et si vous voulez prendre la deuxième approche, vous pouvez rendre le code beaucoup plus clair en ayant deux séparés de verrouillage des blocs séparés par l'attendent expression:

// 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'attente dans la serrure de bloc lui-même, la langue est de vous forcer à penser à ce que vous avez vraiment envie de faire, et de faire des choix plus clair dans le code que vous écrivez.

18voto

hans Points 374

Cela renvoie à 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 sur ceci:

La async/await le langage permet de nombreuses choses assez facile, mais il introduit aussi un scénario qui a été rarement rencontre avant, il était tellement facile d'utiliser les appels asynchrones: re-ouverture.

Cela est particulièrement vrai pour les gestionnaires d'événements, car pour de nombreux événements vous n'avez pas la moindre idée de ce qui se passe après le retour à partir du gestionnaire d'événements. Une chose qui pourrait effectivement se produire, c'est que la méthode async que vous êtes en attente dans le premier gestionnaire d'événements est appelé à partir d'un autre gestionnaire d'événement encore sur la même thread.

Ici est un véritable scénario je suis tombé sur un windows 8 App store: Mon application a deux cadres: à venir dans et en laissant partir d'une image je veux charger/sécurité certaines données dans un fichier/de stockage. OnNavigatedTo/à Partir d'événements sont utilisés pour le chargement et la sauvegarde des. L'enregistrement et le chargement se fait par certains async fonction d'utilité (comme http://winrtstoragehelper.codeplex.com/). Lors de la navigation à partir de l'image 1 à l'image de 2 ou dans l'autre sens, l'asynchrone en charge et la sécurité des opérations sont appelées et attendu. Les gestionnaires d'événements devenir async retour void => ils ne peuvent pas être attendu.

Cependant, la première opération ouvrir fichier (permet de dit: à l'intérieur d'une fonction d'enregistrement) de l'utilitaire est asynchrone trop et donc, les premiers attendent les retours de contrôle pour le cadre, qui quelque temps plus tard, en appelle un autre utilitaire (charge) par l'intermédiaire de la deuxième gestionnaire d'événement. La charge maintenant essaie d'ouvrir le même fichier, et si le fichier est ouvert en maintenant pour l'opération d'enregistrement, échoue avec une ACCESSDENIED exception.

Un minimum pour moi, la solution est de sécuriser les accès à un fichier par un aide 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 le son de verrouillage de coeur verrouille toutes les opération de fichier de l'utilitaire avec une seule serrure, qui est inutilement forte, mais fonctionne très bien pour mon scénario.

Ici est mon projet de test: un windows 8 app store avec quelques appels de test pour la version d'origine de l' 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.

Permettez-moi de suggérer ce lien: http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx

2voto

Pogonets Anton Points 33

Hmm, moche, semble fonctionner.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X