线程重命名

在Java中,可以重命名线程。 在.NET中它不是。 这是因为Name是Thread类中的一次性属性:

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); } } } 

鉴于Java允许线程重命名并且所使用的大多数底层线程结构都是在两个平台上提供操作系统,我倾向于认为我实际上可以在C#中重命名一个线程,如果我避免使用某些function)我不在乎或b)根本不使用。

你知道为什么Thread重命名是一次写入操作吗? 如果更改名称有什么想法吗?

我已经尝试过测试,我将线程重命名为:

 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}); 

结果是名称确实已更改,这反映在使用此线程的其他代码上。 这样做的背景是我需要记录线程所做的事情以及我使用的日志库(log4net)使用Thread.Name来指定哪个线程执行哪个操作。 提前致谢。

编辑:请停止建议明显的事情! 如果我问如何重命名它,我知道如何在开始时命名一个线程。

我需要这样做的原因是,线程将被重用,并且它可能被另一个组件使用,我想表示这个,如果有时会发生日志记录,以便有一个特定的线程名称而不是通用数字。

我使用了Reflector的analyze操作和我看到的BCL中的唯一代码(或者更确切地说是Nikolaos看到的)使用Thread.Name getter的是在user32.dll中调用RegisterClassEx API。 Thread类本身仅引用Name getter和setter中的m_Name成员。 我怀疑以你采用的方式重命名线程是安全的。 除了我将更改您的代码以获取Thread.Name将具有的同一对象的锁定。 幸运的是,这不过是Thread实例本身,所以很容易做到。

 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}); } 

另外需要注意的是,您可能遇到代码访问安全性问题以及根据应用程序具有的信任级别更改私有成员。 显然,您的测试似乎没有发挥作用,但这里值得一提。

操作系统级别的线程没有名称。 真的,这只是一个方便的function。

我不会以你的方式更改线程的名称。 虽然您正在执行写入多次操作所需的相同操作,但您不知道没有行为依赖于写入操作一次。

例如,调试器(如果获取线程名称)可以缓存该名称,而不必再为其调用该对象。

这里还有一个设计问题,为什么你依赖于线程名称来帮助你的日志记录; 您依靠记录器的行为来指示您尝试记录的操作的一部分。

如果要捕获某个语义,则不应该使记录器和线程符合将捕获该语义的模式。 相反,在特定时间点明确指示记录器的语义。

InformThreadNameChangeEx()在.NET Framework 4.0上不可用(但InformThreadNameChange()是)。

因此,更通用的解决方案将是

 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"; } 

.NET(和Java)中的线程名称仅用于调试和诊断目的。 虽然因为Java可以重命名其.NET可以做同样的线程的逻辑是错误的(因为.NET线程是具有附加function的系统线程的包装器,就像Java线程一样,但它们在其他方面是不相关的),更改线程名称本身没有任何损害,除了在未来版本中存在破坏的风险,因为您使用的是非公共API。

但是,你有什么理由改变它? 我认为它是只读的,以避免创建执行各种任务的“厨房接收器”线程。 当然也有例外,我会提醒您考虑一个需要这个设计的设计是否正确。

可能的解决方法是使实例类包含thead ID字典 – “name”键值对。

您的记录器需要返工,但可以调用字典将“名称”插入日志语句中。

我遇到了同样的问题,因为我正在使用刚刚重用的线程池线程。

今天我点击这个,正如我的代码即将投入生产:-(我无法理解这个设计决定的原因。

我的修复是将线程名称推入log4net NDC上下文堆栈并使用%ndc模式记录它。 如果你的某些线程没有设置NDC,那么这个答案也很有用。

更改名称或尝试更改名称可能会破坏某些内容。 如果System.Threading.Thread的实现发生更改,以便字段m_Name被称为m_ThreadName ,例如,在.NET Framework的未来版本中,或者实际上是Service Pack或hot-fix(尽管可能不太可能),代码将抛出exception。

有一定的可能性依赖于名称是不可变的,这就是设计所要求的。 通过打破这种封装,你冒着破坏(善意)依赖于此的东西的风险。 我不能举一个具体的例子,但由于这个属性可以任何可能的方式使用,问题是无限的。 作为一个理论示例,某些东西可以使用线程的名称作为集合中的键。

我很想知道是否有一个丢失的具体例子,因为我也使用log4net,看看你在驾驶什么。 🙂

更新

这肯定引起了我作为log4net用户的兴趣。 不想维护log4net的分支,这是一个更安全的可能解决方案。

  1. 写一个log4net的包装器,即一个ILog类型的接口(我已经有一个15分钟的工作)。

  2. 使用线程局部变量技术在组件的入口点记录线程名称(例如,使用Extension方法Thread.LoggingName =“blah blah blah”)。

  3. 在日志记录包装器中,临时更改线程名称,然后在记录后再将其更改回来。

这需要重新命名线程并处理它们重新命名它们,这样如果没有命名线程的东西注销它将不会使用不正确的名称注销。

更新2

这个技术的一个粗略的例子:

 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... } 

vinzbe的上述答案是我发现有用的。 Brain Gideon的答案我遇到的问题是InformThreadNameChange(.net 4.0)需要ThreadHandle结构。 因此,只是执行上述操作不会通知VS发生了名称更改,但是您可以在我的包含代码中看到,在将Name设置为null后,将胎面名称设置为null将传播。

感谢您所有的帮助

 ///  /// Class ThreadName. ///  public class ThreadName { ///  /// Updates the name of the thread. ///  /// Name of the string. /// The parameter objects. /// if strName is null, just reset the name do not assign a new one 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(); } } ///  /// Reset the name of the set thread. ///  /// The thread. /// Thread cannot be null 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; } } } }