2010-07-28 16 views
14

En Java, es posible cambiar el nombre de los hilos. En .NET no lo es. Esto se debe a que el nombre es una propiedad que es de una sola escritura en la clase Thread:Cambio de nombre de hilo

public string Name 
{ 
    get 
    { 
     return this.m_Name; 
    } 
    [HostProtection(SecurityAction.LinkDemand, ExternalThreading=true)] 
    set 
    { 
     lock (this) 
     { 
      if (this.m_Name != null) 
      { 
       throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_WriteOnce")); 
      } 
      this.m_Name = value; 
      InformThreadNameChangeEx(this, this.m_Name); 
     } 
    } 
} 

Teniendo en cuenta el hecho de que Java permite cambiar el nombre de la rosca y la mayor parte de las estructuras de hilos de base utilizados son OS-suministra en ambas plataformas, yo Me inclino a pensar que realmente podría cambiar el nombre de un hilo en C#, si evito un cierto conjunto de funcionalidades que a) No me importa o b) No uso en absoluto.

¿Tiene alguna idea de por qué el cambio de nombre de subprocesos es una operación de escritura única? ¿Alguna idea de si cambiar el nombre rompe algo?

He tratado de una prueba donde puedo cambiar el nombre del hilo como tal:

var t1 = new Thread(TestMethod); 
t1.Name = "abc"; 
t1.Start(); 
t1.GetType().GetField("m_Name", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(t1, "def"); 
t1.GetType().GetMethod("InformThreadNameChangeEx", BindingFlags.NonPublic | BindingFlags.Static).Invoke(t1, new object[] { t1, t1.Name}); 

El resultado es que el nombre se cambió de hecho y esto se refleja en el otro código que utiliza este hilo. El fondo de esto es que necesito registrar cosas que hacen los hilos y la biblioteca de registro que uso (log4net) usa Thread.Name para especificar qué hilo hace qué acción. Gracias por adelantado.

EDITAR: Por favor, deje de sugerir cosas obvias! Sé cómo nombrar un hilo al inicio si estoy preguntando cómo RE-nombrarlo.

La razón por la que tengo que hacer esto, es que el hilo será reutilizado y puede ser utilizado por otro componente y quiero indicar esto, siempre y cuando haya un registro, para tener un nombre de hilo específico, en lugar de un número genérico.

+1

¿por qué quiere cambiar de nombre a hread? ¿Aparte de porque Java puede hacerlo? – ChrisF

+1

@ChrisF: ver la edición anterior. –

+0

Bastante justo, aunque podrías derivar un 'LoggingThread' de' Thread' que expone una propiedad de lectura/escritura llamada 'ThreadName' y usar eso en su lugar. Sé que es una propiedad extra que tiene el mismo valor, pero hace lo que quiere y no depende de la reflexión, que puede romperse. – ChrisF

Respuesta

6

que utiliza la operación analizar desde Reflector y el único código en el BCL que vi (o más precisamente Nikolaos vi) que utiliza el captador fue una llamada a la API RegisterClassEx en user32.dll. La clase Thread solo se refiere al miembro m_Name en el getter y setter Name. Sospecho que es seguro cambiar el nombre del hilo de la manera que ha adoptado. Excepto que cambiaría su código para adquirir un bloqueo en el mismo objeto que tendría. Afortunadamente, eso no es otro que la instancia Thread en sí, así que es fácil de hacer.

var t1 = new Thread(TestMethod); 
t1.Name = "abc"; 
t1.Start(); 
lock (t1) 
{ 
    t1.GetType(). 
     GetField("m_Name", BindingFlags.Instance | BindingFlags.NonPublic). 
     SetValue(t1, "def"); 
    t1.GetType(). 
     GetMethod("InformThreadNameChangeEx", BindingFlags.NonPublic | 
      BindingFlags.Static). 
     Invoke(t1, new object[] { t1, t1.Name}); 
} 

Otra cosa a destacar es que es posible que tenga problemas con la seguridad de acceso al código y el cambio de los miembros privados en función de cuál es el nivel de confianza que la aplicación tiene. Obviamente, eso no pareció entrar en juego con su prueba, pero vale la pena mencionarlo aquí.

+1

También ejecuté el análisis y me mostró que MS.Win32.HwndWrapper..ctor (...) usa el captador de Thread.Name. Lo usa para establecer la "cadena lpszClassName" de una nueva ventana ... – Nikolaos

+0

@Nikolaos: Sí, buen truco. No estoy seguro de cómo me perdí eso la primera vez. –

+0

InformThreadNameChangeEx() no está disponible en .NET framework 4.0. Ver http://stackoverflow.com/a/18823380/1418147 a continuación. – John

1

Los nombres de subprocesos en .NET (y Java) se usan exclusivamente para fines de depuración y diagnóstico. Si bien la lógica de que debido a que Java puede cambiar el nombre de sus subprocesos para que .NET pueda hacer lo mismo, es incorrecta (porque un subproceso de .NET es un derivador sobre un subproceso de sistema con funcionalidad adicional, como un subproceso Java, pero no están relacionados), no hay daño per se al cambiar el nombre del hilo, aparte de arriesgarse a romperse en versiones futuras ya que está usando una API no pública.

Sin embargo, ¿qué motivo tiene para cambiarlo? Creo que se hizo de solo lectura para evitar la creación de hilos de "sumidero de cocina" que hacen todo tipo de tareas. Si bien hay excepciones, por supuesto, le advierto que considere si un diseño que requiere esto es el diseño correcto.

+0

Sí, harían mejor en agregar una etiqueta local o algo por el estilo. –

+0

@ Adam: No es mi decisión de diseño, log4net usa Thread.Name para registrar el nombre del hilo actual. Entonces, si quiero registrar un nombre significativo, debo cambiar el nombre del hilo, ya que los hilos se vuelven a usar en diferentes componentes y quiero indicar cuál. –

+0

@_NT: log4net es de código abierto. Si hace algo inconveniente, arréglenlo. –

10

Los subprocesos, en el nivel del sistema operativo, no tienen nombres. Realmente, es solo una característica de conveniencia.

+0

Estrictamente hablando, usted tiene razón, pero puede asociar un nombre con él a través de SEH: vea https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx – GreatAndPowerfulOz

0

Existe la posibilidad definitiva de que algo se base en que el nombre sea inmutable, que es lo que exige el diseño. Al romper esta encapsulación, corre el riesgo de romper algo que (de buena fe) se basa en esto. No puedo dar un ejemplo concreto, pero dado que esta propiedad se puede usar de cualquier manera concebible, el problema no tiene límites. Como ejemplo teórico, algo podría usar el nombre del hilo como clave en una colección.

Me encantaría saber si hubo un ejemplo concreto sin importancia, ya que yo también uso log4net y veo a qué se dirige. :)

actualización

Esto sin duda ha despertado mi interés como un usuario log4net. Al no querer mantener un fork de log4net, esta es una posible solución que es más segura.

  1. Escriba un contenedor para log4net, es decir, una interfaz de tipo ILog (ya tengo uno y 15 minutos de trabajo).

  2. Utilice una técnica de variable local de subprocesos para registrar un nombre de subproceso (por ejemplo, con un método de extensión Thread.LoggingName = "blah blah blah") en los puntos de entrada a sus componentes.

  3. En su contenedor de registro, cambie temporalmente el nombre del hilo y luego vuelva a cambiarlo después del registro.

Este se encarga de cambiar nombres de las discusiones y se ocupa de cambio de nombre de nuevo otra vez, así que si algo que no nombra hilos desconecta éste no podrá conectarse con un nombre incorrecto.

Actualización 2

Un ejemplo aproximado de la técnica:

public class MyComponent 
{ 
    public void EntryPoint() 
    { 
     MyLogger.CurrentLoggerThreadName = "A thread contextual name."; 

     _myLogger.Info("a logging message."); 

     SomeOtherMethod(); 
    } 

    private void SomeOtherMethod() 
    { 
     _myLogger.Info("another logging message with the same thread name."); 
    } 
} 

public class MyLogger 
{ 
    [ThreadStatic] 
    private static string _CurrentLoggerThreadName; 

    private static readonly FieldInfo NameField = typeof(Thread).GetType().GetField("m_Name", BindingFlags.Instance | BindingFlags.NonPublic); 

    public static string CurrentLoggerThreadName 
    { 
     get { return _CurrentLoggerThreadName; } 
     set { _CurrentLoggerThreadName = value; } 
    } 

    private static void LogWithThreadRename(Action loggerAction) 
    { 
     Thread t1 = Thread.CurrentThread; 

     string originalName = (string)NameField.GetValue(t1); 

     try 
     { 
      NameField.SetValue(t1, CurrentLoggerThreadName); 
      loggerAction(); 
     } 
     finally 
     { 
      NameField.SetValue(t1, originalName); 
     } 
    } 

    public void Info(object message) 
    { 
     LogWithThreadRename(() => _iLog.Info(message)); 
    } 

    //More logging methods... 
} 
+0

Me doy cuenta de que este es un indicador tan claro como usted obtener de los programadores que trabajan en System.Threading que el nombramiento si (de acuerdo con su comprensión) muy importante. Pero me pregunto si alguien sabe exactamente qué depende de esto. (Los desarrolladores de Mono te estoy mirando) –

+0

@_NT No estoy pensando solo en el framework. Es una propiedad pública, por lo que cualquier cosa podría depender de ella, p. otros marcos y herramientas que usas –

+0

Actualización muy interesante. ¿Puede explicar más detalladamente el punto 2, cuál es la técnica de variables locales de hilos que tiene en mente? Y en 3, ¿se refiere a cambiar el campo de respaldo "m_Name" solamente (es decir, sin llamar al método de notificación nativo) y luego configurarlo de nuevo? Lo último parece bastante inofensivo, aunque depende del nombre del campo. –

4

no cambiaría el nombre de la rosca de la manera que son. Mientras hace lo mismo que requeriría una operación de escritura múltiple, no sabe que no hay comportamientos que dependan de que la operación sea de escritura única.

Por ejemplo, el depurador, si recibe el nombre del subproceso, puede almacenar en caché ese nombre, y ya no tiene que llamar al objeto para ello.

También hay una pregunta de diseño aquí, de por qué depende del nombre del hilo para ayudar con su registro; confía en el comportamiento del registrador para indicar parte de la operación que está intentando iniciar sesión.

Si desea capturar una cierta semántica, no debe configurar el registrador y el hilo para que se ajusten a un patrón que capturará esa semántica. En su lugar, explícitamente indique la semántica al registrador en el punto particular en el tiempo.

+0

Para mí es claro (y he tratado de explicar esto en la pregunta anterior) que este es un indicador de los programadores que trabajan en System.Threading que esto no debería alterarse. Pero, ¿alguien sabe exactamente qué depende de esto? –

+0

@_NT: Es como preguntar qué depende de GetType(). Es una propiedad públicamente disponible en un tipo, es casi imposible decir qué depende de él o qué suposiciones se hacen debido al hecho de que es de escritura única. – casperOne

+0

¡Vea la respuesta de Brian Gideon! –

0

Cambiar el nombre, o intentar cambiar el nombre, bien podría romper algo. Si la implementación de System.Threading.Thread cambia para que el campo m_Name se llame m_ThreadName, por ejemplo, en una versión futura de .NET Framework, o incluso un paquete de servicio o hot-fix (por improbable que sea), su código arrojará un excepción.

+1

De hecho, pero no planeo usar lo anterior, simplemente estoy comprobando si funciona sin fallar algo inmediatamente: funciona a este nivel -VERY- experimental. –

1

Un posible problema sería tener una clase de instancia que contenga el diccionario de ID de thead - pares de valores de clave "name".

Su registrador necesitaría volver a trabajar, pero se podría llamar al diccionario para insertar el "nombre" en la declaración de registro.

Estoy teniendo el mismo problema, ya que estoy usando subprocesos de grupo de subprocesos que solo se vuelven a utilizar.

2

InformThreadNameChangeEx() no está disponible en .NET Framework 4.0 (pero InformThreadNameChange() es).

Así que una solución más genérica habría

var t1 = new Thread(TestMethod); 
t1.Name = "abc"; 
t1.Start(); 
lock (t1) 
{ 
    t1.GetType(). 
     GetField("m_Name", BindingFlags.Instance | BindingFlags.NonPublic). 
     SetValue(t1, null); 
    t1.Name = "def"; 
} 
0

La respuesta anterior por vinzbe era lo que he encontrado para ser útil. La respuesta de Brain Gideon tuve el problema de que se requiere la estructura ThreadHandle para InformThreadNameChange (.net 4.0). Por lo tanto, simplemente hacer lo anterior no le informará a VS que se produjo el cambio de nombre, sin embargo, puede ver en mi código incluido que después de establecer el Nombre en nulo, se establece el nombre de la banda de rodadura para anularlo.

Gracias por toda su ayuda

/// <summary> 
/// Class ThreadName. 
/// </summary> 
public class ThreadName 
{ 
    /// <summary> 
    /// Updates the name of the thread. 
    /// </summary> 
    /// <param name="strName" type="System.String">Name of the string.</param> 
    /// <param name="paramObjects" type="System.Object[]">The parameter objects.</param> 
    /// <remarks>if strName is null, just reset the name do not assign a new one</remarks> 
    static public void UpdateThreadName(string strName, params object[] paramObjects) 
    { 
     // 
     // if we already have a name reset it 
     // 
     if(null != Thread.CurrentThread.Name) 
     { 
      ResetThreadName(Thread.CurrentThread);     
     } 

     if(null != strName) 
     { 
      StringBuilder sbVar = new StringBuilder(); 
      sbVar.AppendFormat(strName, paramObjects); 
      sbVar.AppendFormat("_{0}", DateTime.Now.ToString("yyyyMMdd-HH:mm:ss:ffff")); 
      Thread.CurrentThread.Name = sbVar.ToString(); 
     } 
    } 

    /// <summary> 
    /// Reset the name of the set thread. 
    /// </summary> 
    /// <param name="thread" type="Thread">The thread.</param> 
    /// <exception cref="System.NullReferenceException">Thread cannot be null</exception> 
    static private void ResetThreadName(Thread thread) 
    { 
     if(null == thread) throw new System.NullReferenceException("Thread cannot be null"); 
     lock(thread) 
     { 
      // 
      // This is a private member of Thread, if they ever change the name this will not work 
      // 
      var field = thread.GetType().GetField("m_Name", BindingFlags.Instance | BindingFlags.NonPublic); 
      if(null != field) 
      { 
       // 
       // Change the Name to null (nothing) 
       // 
       field.SetValue(thread, null); 

       // 
       // This 'extra' null set notifies Visual Studio about the change 
       // 
       thread.Name = null; 
      } 
     } 
    } 
} 
1

me golpean esto hoy en día, al igual que el código estaba a punto de ir a la producción :-(y no puedo entender la razón de esta decisión de diseño.

Mi solución fue empujar el nombre hilo en el log4net NDC context stack & conectarse usando el patrón %ndc. Si algunos de los hilos no se establezca el NDC continuación this answer también es útil.