2009-08-14 13 views
6

Tengo el siguiente código:C# Roscado/bloqueo confusión

var items = new List<string> {"1", "2", "3"}; // 200 items 
foreach(var item in items) { 
    ThreadPool.QueueUserWorkItem((DoWork), item); 
} 

private void DoWork(object obj) 
{ 
    lock(this) 
    { 
    using(var sw = File.AppendText(@"C:\somepath.txt") 
    { 
     sw.WriteLine(obj); 
    } 
    } 
} 

Debido a la rosca, por alguna razón, me sale un número al azar de los 200 artículos escritos a cabo en el fichero. 60 o 127 o, a veces, solo 3. Si elimino el ThreadPool y solo escribo dentro del bucle foreach original, todos los 200 elementos se escriben con éxito.

¿No está seguro de por qué ocurre esto?

Gracias por su ayuda.

Respuesta

9

La siguiente nota de MSDN documentation on ThreadPool lo dice todo:

Las discusiones en el grupo de subprocesos logrado son las discusiones de fondo. Es decir, sus propiedades IsBackground son verdaderas. Esto significa que un subproceso ThreadPool no mantendrá una aplicación ejecutándose después de que todos los subprocesos de primer plano hayan salido de.

Su aplicación simplemente sale (al llegar al final de Main) antes de que sus hilos terminen de ejecutarse.

+1

Una forma de solucionar esto es utilizar Interlocked.Increment para realizar un seguimiento de cuántas veces se llama a DoWork, estableciendo un evento de restablecimiento automático cuando se alcanza el número mágico. El hilo principal puede esperar ese evento en lugar de apagarse. –

+0

¿No hay una forma de decirle al hilo principal que espere hasta que todos los hilos de ThreadPool hayan terminado con el trabajo? – shahkalpesh

+1

El comentario de Steven explica cómo hacerlo. –

2

Esta es una versión simple de lo que estaba aludiendo. Utiliza un solo evento y no sondea o gira, y está escrito para ser reutilizable y permite múltiples conjuntos de trabajo al mismo tiempo. Las expresiones lambda se pueden descartar, si es más conveniente para la depuración.

class Program 
{ 
    static void Main(string[] args) 
    { 
     var items = new string[] { "1", "2", "3", "300" }; 
     using (var outfile = File.AppendText("file.txt")) 
     { 
      using (var ws = new WorkSet<string>(x => 
        { lock (outfile) outfile.WriteLine(x); })) 
       foreach (var item in items) 
        ws.Process(item); 
     } 
    } 

    public class WorkSet<T> : IDisposable 
    { 
     #region Interface 

     public WorkSet(Action<T> action) 
     { _action = action; } 

     public void Process(T item) 
     { 
      Interlocked.Increment(ref _workItems); 
      ThreadPool.QueueUserWorkItem(o => 
        { try { _action((T)o); } finally { Done(); } }, item); 
     } 

     #endregion 
     #region Advanced 
     public bool Done() 
     { 
      if (Interlocked.Decrement(ref _workItems) != 0) 
       return false; 

      _finished.Set(); 
      return true; 
     } 

     public ManualResetEvent Finished 
     { get { return _finished; } } 

     #endregion 
     #region IDisposable 

     public void Dispose() 
     { 
      Done(); 
      _finished.WaitOne(); 
     } 

     #endregion 
     #region Fields 

     readonly Action<T> _action; 
     readonly ManualResetEvent _finished = new ManualResetEvent(false); 
     int _workItems = 1; 

     #endregion 
    } 
} 
0

¿Qué tal short and sweet?

+0

Bien, este es el algoritmo que publiqué, solo eliminando toda reutilización. También requiere que sepa cuántos elementos de trabajo habrá con antelación, mientras que la solución que ofrecí no lo hace. Sin embargo, refuta la sugerencia de Spencer de que este algoritmo sea en sí mismo largo. –

+0

Eso es. La razón por la que lo publiqué es que, teniendo en cuenta la pregunta, el OP todavía no es muy bueno en multi-threading y esta versión corta resuelve su problema al tiempo que demuestra claramente un método de sincronización de hilo rudimentario. –

+0

Entiendo su motivación, pero no me preocupa el hecho de que use un objeto genérico para bloquear en lugar de FileStream. –