Pregunta

Di que tengo este método simple:

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

¿Cómo harías seguro este hilo? Y con eso quiero decir que obtendrías ese enumerador una vez y que múltiples hilos manejen todos los números sin que nadie obtenga duplicados.

Supongo que es necesario utilizar un bloqueo en algún lugar, pero ¿dónde debe estar ese bloqueo para que un bloqueo de iterador sea seguro para subprocesos? ¿Qué necesita, en general, recordar si desea un IEnumerable < T > seguro para subprocesos? O mejor dicho, creo que sería un hilo seguro IEnumerator < T > ...?

¿Fue útil?

Solución

Hay un problema inherente al hacerlo, porque IEnumerator < T > tiene tanto MoveNext () como Current . Realmente quieres una sola llamada como:

bool TryMoveNext(out T value)

en ese punto, puede atómicamente moverse al siguiente elemento y obtener un valor. Implementar eso y seguir siendo capaz de usar yield podría ser complicado ... aunque lo pensaré. Creo que deberías envolver el " no-threadsafe " iterador en uno seguro para subprocesos que ejecutó atómicamente MoveNext () y Current para implementar la interfaz que se muestra arriba. No sé cómo volvería a incluir esta interfaz en IEnumerator < T > para que pueda usarla en foreach aunque ...

Si está utilizando .NET 4.0, las Extensiones paralelas pueden ser capaces de ayudarlo, aunque tendría que explicar más sobre lo que está tratando de hacer.

Este es un tema interesante, es posible que tenga que hacer un blog al respecto ...

EDITAR: ahora he blogeado al respecto con dos enfoques .

Otros consejos

Acabo de probar este fragmento de código:

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 () siempre llama a la parte final del código. Si desea que su IEnumerable sea seguro para subprocesos, agregue los bloqueos de subprocesos que desee en lugar de líneas de escritura, elimínelo usando ReaderWriterSlimLock, Semaphore, Monitor, etc.

Supongo que necesitas una enumeración segura para subprocesos, por lo que probablemente deberías implementar esa.

Bueno, no estoy seguro, pero ¿quizás con algunos bloqueos en la persona que llama?

Borrador:

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

Estaba pensando que no puede hacer que la palabra clave yield sea segura para subprocesos, a menos que haga que dependa de una fuente de valores ya segura para subprocesos:

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
}

Tendrá que decidir si cada inicio típico de un enumerador debe restablecer la secuencia, o si el código del cliente debe hacer eso.

Puedes devolver una secuencia completa cada vez en lugar de usar el rendimiento:

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top