2011-02-03 8 views
6

Gente,CallContext.SetData() - ¿está el objeto disponible cuando el hilo se activa-inactivo-activo (TPL)?

Digamos que almaceno tres nuevas instancias del objeto Coche utilizando CallContext.SetData() del hilo 10, 11, 12. Esos hilos terminan de ejecutarse. Luego realizo otra operación de subprocesos múltiples (tal vez una operación diferente a la primera) que usa los subprocesos 10, 11, 12. ¿GetData() recuperará los mismos tres objetos que almacené? ¿O el contexto de alguna manera es diferente ahora y esos objetos desaparecen?

Mi caso de uso particular es la Biblioteca de tareas paralelas. Estoy usando TPL para paralelizar algunas operaciones y quiero entender qué sucede con los datos almacenados a través de CallContext.SetData() entre las llamadas TPL.

EDITAR
por @wageoghe sugerencia Probé ThreadLocal y funcionó!

código actualizado para probarlo a cabo:

using System; 
using System.Threading; 
using System.Threading.Tasks; 

namespace TlsTest 
{ 

    public class Program 
    { 

     public static void Main() 
     { 
      Console.WriteLine("-------using threadpool---------"); 
      UseThreadPool(); 
      Console.WriteLine("-------using tasks---------"); 
      UseTasks(); 
      Console.WriteLine("-------using parallel for---------"); 
      UseParallelFor(); 
      Console.ReadKey(); 
     } 

     public static void UseThreadPool() 
     { 

      var finish = new CountdownEvent(TotalThreads); 

      for (int i = 0 ; i < TotalThreads ; i++) 
      { 
       ThreadPool.QueueUserWorkItem(x => 
       { 
        int id = Thread.CurrentThread.ManagedThreadId; 

        Thread.Sleep(SleepMilliseconds); 

        if (ThreadId.IsValueCreated) 
        { 
         Console.WriteLine("thread [{0}], tls.thread [{1}] - value already in Tls" , id , ThreadId.Value);       
        } 
        else 
        {       
         Console.WriteLine("thread [{0}] - no Tls value" , id); 
         ThreadId.Value = id; 
        } 
        Thread.Sleep(SleepMilliseconds); 
        finish.Signal(); 
       }); 
      } 
      finish.Wait(); 
     } 

     public static void UseTasks() 
     { 
      const TaskCreationOptions taskCreationOpt = TaskCreationOptions.None; 

      var allTasks = new Task[ TotalThreads ]; 
      for (int i = 0 ; i < TotalThreads ; i++) 
      { 
       Task task = Task.Factory.StartNew(() => 
       { 
        int id = Thread.CurrentThread.ManagedThreadId; 

        Thread.Sleep(SleepMilliseconds); 

        if (ThreadId.IsValueCreated) 
        { 
         Console.WriteLine("thread [{0}], tls.thread [{1}] - value already in Tls" , id , ThreadId.Value); 
        } 
        else 
        { 
         Console.WriteLine("thread [{0}] - no Tls value" , id); 
         ThreadId.Value = id;       
        } 

        Thread.Sleep(SleepMilliseconds); 

       } , taskCreationOpt); 
       allTasks[ i ] = task; 
      } 
      Task.WaitAll(allTasks); 
     } 

     public static void UseParallelFor() 
     { 

      var options = new ParallelOptions(); 
      options.MaxDegreeOfParallelism = 8; 
      Parallel.For(0 , TotalThreads , options , i => 
      { 
       int id = Thread.CurrentThread.ManagedThreadId; 

       Thread.Sleep(SleepMilliseconds); 

       if (ThreadId.IsValueCreated) 
       { 
        Console.WriteLine("thread [{0}], tls.thread [{1}] - value already in Tls" , id , ThreadId.Value); 
       } 
       else 
       { 
        Console.WriteLine("thread [{0}] - no Tls value" , id); 
        ThreadId.Value = id;           
       } 

       Thread.Sleep(SleepMilliseconds); 

      });    
     } 

     private static readonly ThreadLocal<int> ThreadId = new ThreadLocal<int>(); 
     private const int TotalThreads = 100; 
     private const int SleepMilliseconds = 500; 

    }  
} 

Respuesta

7

[ACTUALIZACIÓN]

En realidad, mi respuesta orignal (al final de este post) parece ser en parte equivocada!

Escribí un pequeño programa de prueba que prueba un escenario de almacenamiento de datos en CallContext desde hilos y desde Tareas, hilos ThreadPool y desde hilos en Paralelo.Para. Tanto en la prueba de Tareas como en la prueba de ThreadPool, los datos almacenados en CallContext no se volvieron a ver cuando se reutilizó el mismo hilo (según lo determinado por el ManagedThreadId). Sin embargo, en el caso de Parallel.For, los datos almacenados en el CallContext se volvieron a ver cuando se reutilizó el mismo hilo (según lo determinado por ManagedThreadId). Me pareció muy interesante. No estoy seguro de que se esperen esos resultados o si hay algún problema con mi programa.

Para probar cada caso, simplemente elimine el comentario de la función de prueba deseada.

Verá que las tareas y los subprocesos de ThreadPool nunca encuentran los datos de CallContext en las subsecuentes reutilizaciones de un hilo mientras que Paralelo.Para hilos encuentran los datos de CallContext.

El comportamiento de Parallel.For parece ser inconsistente. Cuando ejecuto el caso Paralelo, puedo ver que un hilo determinado NO siempre encuentra los datos de CallContext cuando ese hilo se reutiliza. Por ejemplo, aquí es la salida de una carrera del programa (con UseParallelFor sin comentar):

thread [9] - no CallContext value 
thread [10] - no CallContext value 
thread [11] - no CallContext value 
thread [12] - no CallContext value 
thread [9], cc.thread [9] - value already in CallContext <-- this is expected as this is the main thread 
thread [10] - no CallContext value 
thread [13] - no CallContext value 
thread [11] - no CallContext value 
thread [12] - no CallContext value 
thread [14] - no CallContext value 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [11], cc.thread [11] - value already in CallContext 
thread [13] - no CallContext value 
thread [15] - no CallContext value 
thread [12], cc.thread [12] - value already in CallContext 
thread [16] - no CallContext value 
thread [14] - no CallContext value 
thread [9], cc.thread [9] - value already in CallContext 
thread [10] - no CallContext value 
thread [17] - no CallContext value 
thread [13], cc.thread [13] - value already in CallContext 
thread [15] - no CallContext value 
thread [11] - no CallContext value 
thread [12] - no CallContext value 
thread [14], cc.thread [14] - value already in CallContext 
thread [18] - no CallContext value 
thread [16] - no CallContext value 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [13] - no CallContext value 
thread [15], cc.thread [15] - value already in CallContext 
thread [11], cc.thread [11] - value already in CallContext 
thread [17] - no CallContext value 
thread [19] - no CallContext value 
thread [18] - no CallContext value 
thread [16], cc.thread [16] - value already in CallContext 
thread [14] - no CallContext value 
thread [20] - no CallContext value 
thread [12], cc.thread [12] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [21] - no CallContext value 
thread [15] - no CallContext value 
thread [11], cc.thread [11] - value already in CallContext 
thread [17], cc.thread [17] - value already in CallContext 
thread [13], cc.thread [13] - value already in CallContext 
thread [19] - no CallContext value 
thread [22] - no CallContext value 
thread [18], cc.thread [18] - value already in CallContext 
thread [16] - no CallContext value 
thread [20] - no CallContext value 
thread [14], cc.thread [14] - value already in CallContext 
thread [12], cc.thread [12] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [23] - no CallContext value 
thread [15], cc.thread [15] - value already in CallContext 
thread [21] - no CallContext value 
thread [11], cc.thread [11] - value already in CallContext 
thread [17] - no CallContext value 
thread [13], cc.thread [13] - value already in CallContext 
thread [19], cc.thread [19] - value already in CallContext 
thread [22] - no CallContext value 
thread [16], cc.thread [16] - value already in CallContext 
thread [18] - no CallContext value 
thread [24] - no CallContext value 
thread [20], cc.thread [20] - value already in CallContext 
thread [14], cc.thread [14] - value already in CallContext 
thread [12], cc.thread [12] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10] - no CallContext value 
thread [15], cc.thread [15] - value already in CallContext 
thread [21], cc.thread [21] - value already in CallContext 
thread [17], cc.thread [17] - value already in CallContext 
thread [13], cc.thread [13] - value already in CallContext 
thread [22], cc.thread [22] - value already in CallContext 
thread [18], cc.thread [18] - value already in CallContext 
thread [16], cc.thread [16] - value already in CallContext 
thread [14], cc.thread [14] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [15], cc.thread [15] - value already in CallContext 
thread [17], cc.thread [17] - value already in CallContext 
thread [18], cc.thread [18] - value already in CallContext 
thread [16], cc.thread [16] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [17], cc.thread [17] - value already in CallContext 
thread [18], cc.thread [18] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 

Como se puede ver, no es el caso que una vez que un valor ha sido encontrado en un reutilizados que permanece en el CallContext para siempre. En algunos casos, durante un par de iteraciones de un hilo determinado, el valor no se encuentra en el CallContext, por lo que se agrega. Luego, una iteración informará que se encontró el valor. Entonces, tal vez, la próxima iteración dirá que no se encontró el valor.

Los resultados me dicen que no debe confiar en que los datos permanezcan intactos en el CallContext para un hilo determinado que se limpia entre reutilizaciones del hilo. También me dicen que no debe confiar en que el CallContext se borre entre reutilizaciones del mismo hilo en el caso de Parallel.For.

Aquí es mi programa de pruebas:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

using System.Threading; 
using System.Threading.Tasks; 
using System.Runtime.Remoting.Messaging; 

namespace CallContextTest 
{ 
    class Program 
    { 
    static void Main(string[] args) 
    { 
     //UseTasks(); 
     //UseThreadPool(); 
     UseParallelFor(); 

     Console.ReadKey(); 
    } 

    public static void UseThreadPool() 
    { 
     int totalThreads = 100; 

     CountdownEvent finish = new CountdownEvent(totalThreads); 

     for (int i = 0; i < totalThreads; i++) 
     { 
     int ii = i; 

     ThreadPool.QueueUserWorkItem(x => 
     { 
      int id = Thread.CurrentThread.ManagedThreadId; 

      Thread.Sleep(1000); 

      object o = CallContext.GetData("threadid"); 
      if (o == null) 
      { 
      //Always gets here. 
      Console.WriteLine("thread [{0}] - no CallContext value", id); 
      CallContext.SetData("threadid", id); 
      } 
      else 
      { 
      //Never gets here. 
      Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id); 
      } 

      Thread.Sleep(1000); 
      finish.Signal(); 
     }); 

     } 

     finish.Wait(); 
    } 

    public static void UseTasks() 
    { 
     int totalThreads = 100; 
     TaskCreationOptions taskCreationOpt = TaskCreationOptions.None; 
     Task task = null; 


     Task[] allTasks = new Task[totalThreads]; 
     for (int i = 0; i < totalThreads; i++) 
     { 
     int ii = i; 
     task = Task.Factory.StartNew(() => 
     { 
      int id = Thread.CurrentThread.ManagedThreadId; 

      Thread.Sleep(1000); 

      object o = CallContext.GetData("threadid"); 
      if (o == null) 
      { 
      //Always gets here. 
      Console.WriteLine("thread [{0}] - no CallContext value", id); 
      CallContext.SetData("threadid", id); 
      } 
      else 
      { 
      //Never gets here. 
      Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id); 
      } 

      Thread.Sleep(1000); 

     }, taskCreationOpt); 
     allTasks[i] = task; 
     } 
     Task.WaitAll(allTasks); 
    } 

    public static void UseParallelFor() 
    { 
     int totalThreads = 100; 
     Parallel.For(0, totalThreads, i => 
     { 
     int ii = i; 
     int id = Thread.CurrentThread.ManagedThreadId; 

     Thread.Sleep(1000); 

     object o = CallContext.GetData("threadid"); 
     if (o == null) 
     { 
      //Sometimes gets here. 
      Console.WriteLine("thread [{0}] - no CallContext value", id); 
      CallContext.SetData("threadid", id); 
     } 
     else 
     { 
      //Sometimes gets here as threads are reused. 
      Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id); 
     } 

     Thread.Sleep(1000); 

     }); 
    } 

    } 
} 

Tenga en cuenta, a la luz del programa de prueba anterior y mis nuevos comentarios, en la parte superior de esta respuesta, mi discusión original parece estar mal. De mi prueba, parece que los datos almacenados en CallContext no están disponibles en las reutilizaciones posteriores del mismo id. De subproceso en el caso de las tareas y subprocesos ThreadPool. Sin embargo, parece que los datos almacenados en CallContext están disponibles en reutilizaciones del mismo hilo en el caso de Parallel.For.

Ignore todo después de la Actualización final.

[Actualización Fin]

soy ciertamente ningún experto TPL, pero he estado mirando CallContext.SetData (y LogicalSetData) mucho recientemente, así que tengo una idea de cómo funciona el CallContext. Hay mejores personas aquí en SO para describir lo que puede suceder o no con los datos de CallContext en el "contexto" de TPL.

Mi comprensión de cómo funciona CallContext.SetData es que los "datos" se borran cuando el hilo desaparece. Por lo tanto, si crea un nuevo hilo y, mientras ejecuta en ese hilo, almacena algunos datos con CallContext.SetData, entonces los datos desaparecerán cuando el hilo muera. Si está utilizando un hilo ThreadPool, el hilo nunca muere (bueno, tal vez nunca es demasiado fuerte), por lo que los datos almacenados a través de CallContext.SetData seguirán allí la próxima vez que se ejecute algún código en el hilo (reutilizado).

Según tengo entendido, la Biblioteca paralela de tareas utiliza el ThreadPool internamente, por lo que cualquier dato almacenado mediante CallContext.SetData probablemente aún estará allí cuando se vuelva a utilizar el hilo ThreadPool subyacente.

Debería ser fácil escribir una o más pruebas pequeñas para ver qué sucede cuando coloca datos en CallContext y luego verifica si están allí en usos posteriores del mismo hilo.

+0

muchas gracias por revisar su respuesta y por la explicación detallada! Dado que su ejemplo muestra inconsistencia en si los datos se guardan utilizando CallContext, supongo que la pregunta natural es ¿cómo nos aseguraríamos de que solo se cree un objeto por cada id del subproceso? Supongo que almacenar valores en el AppDomain con la clave que tiene el prefijo de id. De hilo administrado. Y es AppDomain.SetData thread-safe? Espero que otras personas en SO se presenten y nos digan qué está pasando con CallContext. pensamientos? – SFun28

+0

@ SFun28 - Mi suposición es que tal vez uno no debería confiar en el CallContext para que un id. De subproceso en particular sea persistente en todos los usos de ese subproceso. Está bien almacenar algo a través de CallContext.SetData y luego acceder a él desde cualquier parte del hilo, pero probablemente no está bien almacenar algo ahí y esperar que esté asociado con ese ID de hilo para siempre. Supongo que depende del efecto que intentas lograr. Tal vez si le da una descripción más detallada de lo que quiere hacer exactamente en su pregunta original, podría ayudar a la gente a evaluar la mejor manera de lograrlo. – wageoghe

+1

@ SFun28 - FYI - No tengo tiempo en este momento, pero es posible que desee utilizar mi código de muestra para realizar pruebas, pero almacene información usando algunas de las otras instalaciones locales de subprocesos como Thread.SetData, [ThreadStatic], ThreadLocal (http://msdn.microsoft.com/en-us/library/dd642243.aspx). Observe también CallContext.LogicalSetData: es similar a SetData, pero los datos que almacena fluyen a subprocesos secundarios. Busque en el blog de Jeffrey Richter (27 de septiembre de 2010) un artículo sobre LogicalSetData. – wageoghe

Cuestiones relacionadas