Question

Dites que j'ai cette méthode simple:

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

Comment feriez-vous ce fil de sécurité? Et par là, je veux dire que vous obtiendrez cet énumérateur une fois et que plusieurs threads gèrent tous les nombres sans que personne ne reçoive de doublons.

Je suppose qu’un verrou doit être utilisé quelque part, mais qu’il doit être pour qu’un bloc itérateur soit thread-safe? De quoi, en général, devez-vous vous souvenir si vous voulez un IEnumerable < T > sécurisé? Ou plutôt, je suppose que ce serait un IEnumerator < T > ...?

Était-ce utile?

La solution

Cela pose un problème inhérent, car IEnumerator < T > contient à la fois MoveNext () et Actuel . Vous voulez vraiment un seul appel tel que:

bool TryMoveNext(out T value)

à ce stade, vous pouvez atomiquement passer à l'élément suivant et obtenir une valeur. Mettre en œuvre cela et continuer à utiliser rendement pourrait être délicat ... Je vais y réfléchir cependant. Je pense que vous devez envelopper le " non-threadsafe " itérateur dans un environnement thread-safe qui exécute de manière atomique MoveNext () et Current pour implémenter l'interface indiquée ci-dessus. Je ne sais pas comment vous pourriez alors envelopper cette interface dans IEnumerator < T > afin de pouvoir l'utiliser dans foreach si ...

Si vous utilisez .NET 4.0, les extensions parallèles peuvent pouvoir vous aider. Vous devez cependant en savoir plus sur ce que vous essayez de faire.

C’est un sujet intéressant - j’aurai peut-être un blog à faire à ce sujet ...

EDIT: blogué à ce sujet avec deux approches .

Autres conseils

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.

Je suppose que vous avez besoin d'une énumération thread-safe, vous devriez donc probablement l'implémenter.

Eh bien, je ne suis pas sûr, mais peut-être avec des verrous 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);

Je pensais que vous ne pouviez pas rendre le mot clé return thread-safe, sauf si vous le dépendiez 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 initiation typique d'un énumérateur doit réinitialiser la séquence ou si le code client doit le faire.

Vous pouvez simplement renvoyer une séquence complète à chaque fois plutôt que d'utiliser le rendement:

return Enumerable.Range (0, 100) .Cast < uint > (). ToArray ();

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top