代码之家  ›  专栏  ›  技术社区  ›  NT_

线程重命名

  •  14
  • NT_  · 技术社区  · 14 年前

    在Java中,可以重命名线程。在.NET中不是。这是因为名称是在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);
            }
        }
    }
    

    你知道为什么线程重命名是一次写操作吗?你知道改名会破坏什么吗?

    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指定哪个线程执行什么操作。提前谢谢。

    编辑: 请不要再提明显的事情了!如果我问如何重新命名线程,我知道如何在开始时命名它。

    我之所以需要这样做,是因为线程将被重新使用,它可能被另一个组件使用,我想指出这一点,如果以及何时会发生日志记录,以便有一个特定的线程名称,而不是一个通用的编号。

    10 回复  |  直到 14 年前
        1
  •  7
  •   casperOne    14 年前

    我使用了 Reflector 我在BCL中看到的唯一代码(或者更准确地说是Nikolaos看到的)使用 Thread.Name getter是对 RegisterClassEx Thread 类本身只引用 m_Name 中的成员 Name 盖特和塞特。我怀疑以您采用的方式重命名线程是安全的。只是我会修改你的代码来获取同一个对象的锁 线程名称 线程 实例本身,这样做很容易。

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

    另一件需要注意的事情是,根据应用程序的信任级别,代码访问安全性和更改私有成员可能会有问题。显然,这似乎没有发挥你的测试,但值得一提的是在这里。

        2
  •  10
  •   Steven Sudit    14 年前

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

        3
  •  4
  •   casperOne    14 年前

    我不会像你那样改变线程的名称。当您正在做一个write-many操作所需的相同操作时,您不知道没有行为依赖于只写一次的操作。

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

    如果您想捕获某个语义,则不应该将记录器和线程的形状设置为符合将捕获该语义的模式。相反,在特定的时间点显式地向记录器指示语义。

        4
  •  3
  •   tanascius    11 年前

    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";
    }
    
        5
  •  1
  •   Adam Robinson    14 年前

    本身 在更改线程的名称时,除了在将来的版本中因为使用非公共API而有被破坏的风险之外。

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

        6
  •  1
  •   wilf    11 年前

    一个可能的解决方法是使用一个实例类,该类包含一个ID-“name”键值对的字典。

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

        7
  •  1
  •   Community PPrice    7 年前

    我的解决办法是将线程名称推入 log4net NDC context stack &使用 %ndc 图案。如果你的一些线程不能设置NDC,那么 this answer 也很有用。

        8
  •  0
  •   Rob    14 年前

    改变名字,或者试图改变名字,很可能会破坏一些东西。如果执行 System.Threading.Thread 改变,使现场 m_Name 被称为 m_ThreadName ,例如,在.NET Framework的未来版本中,或者实际上是service pack或热修复程序(尽管可能不太可能),您的代码将引发异常。

        9
  •  0
  •   Tim Lloyd    14 年前

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

    我很想知道是否有一个非常具体的例子,虽然我也使用log4net,看看你在说什么。:)

    更新

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

    2. 在日志包装器中临时更改线程名称,然后在日志记录后再次更改。

    更新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...
    }
    
        10
  •  0
  •   user8485350    11 年前

    我发现文兹贝的上述回答很有用。Brain-Gideon的答案是,InformThreadNameChange(.net4.0)需要ThreadHandle结构。因此,只做上述操作不会通知VS名称发生了更改,但是您可以在我包含的代码中看到,在您将名称设置为null之后,将tread name设置为null它将传播。

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