2009-10-22 8 views
38

Decir que tengo este sencillo método:C#: ¿Cómo puedo hacer que un subproceso IEnumerable <T> sea seguro?

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

¿Cómo le hacen de este hilo seguro? Y con eso me refiero a que obtendrías ese enumerador una vez, y harías que múltiples hilos manejen todos los números sin que nadie obtenga duplicados.

Supongo que es necesario usar un bloqueo en alguna parte, pero ¿dónde debe estar ese bloqueo para que un bloque iterador sea seguro para subprocesos? ¿Qué necesita recordar, en general, si quiere un hilo seguro IEnumerable<T>? O mejor dicho, supongo que sería un hilo de seguridad IEnumerator<T> ...?

+4

(En caso de que no esté revisando mis comentarios) Acabo de escribir en un blog acerca de esto: http://msmvps.com/blogs/jon_skeet/archive/2009/10/23/iterating-atomically.aspx –

+2

Oh , ¡Gracias! Este sitio debería tener una especie de notificación de Facebook cuando alguien comenta después de tu comentario o algo así ... jeje. – Svish

Respuesta

38

Existe un problema inherente al hacerlo, porque IEnumerator<T> tiene MoveNext() y Current. ¿De verdad quieres una sola llamada, tales como:

bool TryMoveNext(out T value) 

en ese punto se puede atómicamente pase a la siguiente elemento y obtener un valor. Implementar eso y seguir siendo capaz de usar yield podría ser complicado ... Sin embargo, lo pensaré un poco. Creo que tendrías que ajustar el iterador "sin hilos" en un hilo seguro que atómicamente realizó MoveNext() y Current para implementar la interfaz que se muestra arriba. No sé cómo le gustaría a continuación, envuelve esta interfaz de nuevo en IEnumerator<T> de manera que se podría utilizar en foreach aunque ...

Si está utilizando .NET 4.0, extensiones paralelas pueden ser capaces de ayudar usted - necesitaría explicar más acerca de lo que está tratando de hacer.

Este es un tema interesante - Voy a tener que blog acerca de ello ...

EDIT: Tengo ahora blogged about it con dos enfoques.

+3

Debe llamar a ese 'TryMoveNext' para que coincida con elementos similares en el marco. :) –

+0

@ 280Z28: Buena idea. Editará :) –

+0

Una forma en que puedo pensar que te permitiría usarla en foreach sería tener una clase que implemente IEnumerable, y almacene internamente el elemento actual en un campo threadlocal, de esta forma, cuando un hilo ha llamado con éxito MoveNext, su valor se almacena para su uso posterior, separado de otros hilos. –

1

Supongo que necesita un Enumerator para guardar hilos, por lo que probablemente debería implementarlo.

-2

sólo podría devolver una secuencia completa cada vez más que el rendimiento utilización:

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

+0

No quiere procesar el rango completo en cada hilo. – Guillaume

+0

Y tampoco quiero tener que poner todo el rango en una matriz ni nada de eso. – Svish

0

Bueno, no estoy seguro, pero tal vez con algunos bloqueos en la persona que llama?

Proyecto:

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

Estaba pensando que no se puede hacer que la palabra clave yield flujos seguros, a menos que sea depende de una fuente ya flujos seguros de valores:

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 
} 

Deberá decidir si cada iniciación típica de un enumerador debe restablecer la secuencia, o si el código del cliente debe hacer eso.

1

Acabo de probar este trozo 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() llama siempre el fin parte del código. Si desea que su IEnumerable sea seguro para subprocesos, agregue los bloqueos de subprocesos que desee en lugar de writelines, eliminándolos mediante ReaderWriterSlimLock, Semaphore, Monitor, etc.

Cuestiones relacionadas