40 votes

C #: Comment puis-je créer un IEnumerable <T> fil en sécurité?

Dire que j'ai cette méthode simple:

public IEnumerable<uint> GetNumbers()
{
    uint n = 0;
    while(n < 100)
        yield return n++;
}

Comment voulez-vous faire de ce "thread-safe"? Je veux dire par là que vous obtenez qui énumérateur une fois, et avoir plusieurs threads gérer tous les numéros sans que personne ne se duplique.

Je suppose qu'un verrou doit être utilisé de quelque part, mais où doit-lock pour un itérateur bloc pour être thread-safe? Ce qui, en général, avez-vous besoin de vous rappeler si vous voulez un "thread-safe" IEnumerable<T>? Ou plutôt, je suppose que ce serait un "thread-safe" IEnumerator<T>...?

41voto

Jon Skeet Points 692016

Il y a un problème inhérent à le faire, parce IEnumerator<T> a la fois MoveNext() et Current. Vous voulez vraiment un seul appel, tels que:

bool TryMoveNext(out T value)

à ce stade, vous pouvez automatiquement passer à l'élément suivant et obtenir une valeur. La mise en œuvre qui, tout en étant capable d'utiliser yield pourrait être difficile... je penserai bien. Je pense que vous auriez besoin d'envelopper le "non thread-safe" itérateur dans un "thread-safe" qui atomiquement effectué MoveNext() et Current pour mettre en œuvre l'interface présentée ci-dessus. Je ne sais pas comment vous pouvez envelopper cette interface dos en IEnumerator<T> , de sorte que vous pouvez l'utiliser en foreach si...

Si vous êtes en utilisant .NET 4.0, Parallel Extensions peuvent être en mesure de vous aider, il vous faudrait expliquer plus au sujet de ce que vous essayez de faire.

C'est un sujet intéressant - j'ai peut-être sur ce blog...

EDIT: maintenant j'ai blogué à ce sujet avec les deux approches.

2voto

Dr. K Points 11

Je viens de tester ce morceau de code:

 static IEnumerable<int> getNums()
{
    Console.WriteLine("IENUM - ENTER");

    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine(i);
        yield return i;
    }

    Console.WriteLine("IENUM - EXIT");
}

static IEnumerable<int> getNums2()
{
    try
    {
        Console.WriteLine("IENUM - ENTER");

        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine(i);
            yield return i;
        }
    }
    finally
    {
        Console.WriteLine("IENUM - EXIT");
    }
}
 

getNums2 () appelle toujours la partie finale du code. Si vous souhaitez que votre IEnumerable soit sécurisé pour les threads, ajoutez le verrouillage de thread que vous souhaitez au lieu des lignes d'écriture, utilisez ReaderWriterSlimLock, Semaphore, Monitor, etc.

1voto

Stefan Steinegger Points 37073

Je suppose que vous avez besoin d'un énumérateur de sauvegarde de threads, vous devriez donc probablement l'implémenter.

0voto

Guillaume Points 5649

Eh bien, je ne suis pas sûr, mais peut-être avec des serrures dans l'appelant?

Brouillon:

 Monitor.Enter(syncRoot);
foreach (var item in enumerable)
{
  Monitor.Exit(syncRoot);
  //Do something with item
  Monitor.Enter(syncRoot);
}
Monitor.Exit(syncRoot);
 

0voto

Cecil Has a Name Points 3385

Je pensais que vous ne pouviez pas rendre le mot clé thread-safe yield , à moins de le faire dépendre d'une source de valeurs déjà thread-safe:

 public interface IThreadSafeEnumerator<T>
{
    void Reset();
    bool TryMoveNext(out T value);
}

public class ThreadSafeUIntEnumerator : IThreadSafeEnumerator<uint>, IEnumerable<uint>
{
    readonly object sync = new object();

    uint n;

    #region IThreadSafeEnumerator<uint> Members
    public void Reset()
    {
        lock (sync)
        {
            n = 0;
        }
    }

    public bool TryMoveNext(out uint value)
    {
        bool success = false;

        lock (sync)
        {
            if (n < 100)
            {
                value = n++;
                success = true;
            }
            else
            {
                value = uint.MaxValue;
            }
        }

        return success;
    }
    #endregion
    #region IEnumerable<uint> Members
    public IEnumerator<uint> GetEnumerator()
    {
        //Reset(); // depends on what behaviour you want
        uint value;
        while (TryMoveNext(out value))
        {
            yield return value;
        }
    }
    #endregion
    #region IEnumerable Members
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        //Reset(); // depends on what behaviour you want
        uint value;
        while (TryMoveNext(out value))
        {
            yield return value;
        }
    }
    #endregion
}
 

Vous devrez décider si chaque lancement typique d'un énumérateur doit réinitialiser la séquence ou si le code client doit le faire.

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