开发者

Thread Renaming

开发者 https://www.devze.com 2023-01-09 10:11 出处:网络
In Java, renaming threads is possible. In .NET it is not. This is because the Name is a property that is write-once in the Thread class:

In Java, renaming threads is possible. In .NET it is not. This is because the Name is a property that is write-once in the Thread class:

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

Given the fact that Java allows thread renaming and most of the underlying thread structures used are OS-supplied in both platforms, I'm inclined to think that I could actually rename a thread in C#, if I avoid a certain set of functionality that a) I don't care or b) don't use at all.

Do you have any idea why Thread renaming is a write-once operation? Any idea if changing the name breaks something?

I have tried a test where I rename the thread as such:

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

The result is that the name is indeed changed and this is reflected on other code that uses this thread. The background to this is that I need to log things that threads do and the logging library I use (log4net) uses Thread.Name to specify which thread does what action. Thanks in advance.

EDIT: Please stop suggesting obvious things! I know how to name a thread at start if I am asking how to RE-name it.

The reason why I need to do this, is that the thread will be re-used and it may be used by another component and I want to signify this, if and when there will be logging occuring, so as to have a specific thread 开发者_StackOverflow中文版name, rather than a generic number.


Threads, at the OS level, don't have names. Really, it's just a convenience feature.


I used the analyze operation from Reflector and the only code in the BCL that I saw (or more precisely Nikolaos saw) which uses the Thread.Name getter was a call to the RegisterClassEx API in user32.dll. The Thread class itself only refers to the m_Name member in the Name getter and setter. I suspect it is safe to rename the thread in the manner you have adopted. Except that I would change your code to acquire a lock on the same object that Thread.Name would have. Fortunately that is none other than the Thread instance itself so it is easy to do.

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

Another thing to note is that you might have problems with code access security and changing private members depending on what trust level the application has. Obviously that did not seem to come into play with your test, but it is worth mentioning here.


I would not change the name of the thread in the manner that you are. While you are doing the same thing that a write-many operation would require, you don't know that there aren't behaviors dependent on the operation being write-once.

For example, the debugger, if it gets the thread name, can cache that name, and not have to call the object anymore for it.

There is also a design question here, of why you are depending on the thread name to help with your logging; you are relying on the behavior of the logger to indicate part of the operation you are trying to log.

If you want to capture a certain semantic, you shouldn't shape the logger and the thread to conform to a pattern which will capture that semantic. Instead, explicitly indicate the semantic to the logger at the particular point in time.


InformThreadNameChangeEx() is not available on .NET framework 4.0 (but InformThreadNameChange() is).

So a more generic solution would be

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


Thread names in .NET (and Java) are used purely for debugging and diagnostics purposes. While the logic that because Java can rename its threads that .NET can do the same is faulty (because a .NET thread is a wrapper over a system thread with additional functionality, as is a Java thread, but they're otherwise unrelated), there's no harm per se in changing the thread's name, other than risking breakage in future versions since you're using a non-public API.

However, what reason do you have for changing it? I think it was made read-only to avoid the creation of "kitchen sink" threads that do all manner of tasks. While there are exceptions, of course, I would caution you to consider whether or not a design that requires this is the right design.


A possible work around would be to have an instance class containing dictionary of thead ID - "name" key value pairs.

Your logger would need rework, but the dictionary could be called to insert the "name" into the log statement.

I'm having the same issue, as I am using thread pool threads which just get reused.


I hit this today, just as my code was about to go to production :-( and I can't understand the reason for this design decision.

My fix was to push the thread name into the log4net NDC context stack & log it using the %ndc pattern. If some of your threads won't set the NDC then this answer is also useful.


Changing the name, or attempting to change the name, could well break something. If the implementation of System.Threading.Thread changes so that the field m_Name is called m_ThreadName, for example, in a future version of the .NET Framework, or indeed a service pack or hot-fix (unlikely though that may be), your code will throw an exception.


There is the definite possibility that something relies on the name being immutable, which is what the design is mandating. By breaking this encapsulation you run the risk of breaking something that (in good faith) relies on this. I cannot give a concrete example, but since this property can be used in any conceivable way, the problem is unbounded. As a theoretical example something could use the thread's name as a key in a collection.

I would love to know if there was a drop-dead concrete example though as I too use log4net and see what you are driving at. :)

Update

This has certainly piqued my interest as a log4net user. Not wanting to maintain a fork of log4net, this is a possible solution that is safer.

  1. Write a wrapper for log4net i.e. an ILog type interface (I already have one and 15 minutes work).

  2. Use a thread local variable technique to record a thread name (e.g. with an Extension method Thread.LoggingName = "blah blah blah") at entry points to your components.

  3. In your logging wrapper temporarily change the thread name and then change it back again after logging.

This takes care of re-naming threads and handles re-naming them back again, so that if something that does not name threads logs out it will not log out with an incorrect name.

Update 2

A rough example of the technique:

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


The above answer by vinzbe was what I found to be useful. The answer from Brain Gideon I had the problem that the ThreadHandle structure is required for InformThreadNameChange (.net 4.0). So just doing the above will not inform VS that the name change occurred, however you can see in my included code that after you set the Name to null, the set the tread name to null it will propagate.

Thanks for all of your help

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

精彩评论

暂无评论...
验证码 换一张
取 消