Переименование темы - PullRequest
       12

Переименование темы

14 голосов
/ 28 июля 2010

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

Учитывая тот факт, что Java допускает переименование потоков, и большинство используемых базовых структур потоков предоставляются ОС на обеих платформахЯ склонен думать, что на самом деле я мог бы переименовать поток в C #, если бы я избегал определенного набора функций, которые а) меня не волнуют или б) вообще не используют.

Есть ли у вас идеи, почему переименование потока является операцией однократной записи?Любая идея, если изменение имени что-то нарушает?

Я пробовал тест, в котором я переименовал поток следующим образом:

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 ]

10 голосов
/ 28 июля 2010

Потоки на уровне ОС не имеют имен. Действительно, это просто удобная функция.

6 голосов
/ 28 июля 2010

Я использовал операцию анализа из Reflector и единственный код в BCL, который я видел (или, точнее, увидел Николаос), который использует геттер Thread.Name, был вызовом RegisterClassEx API user32.dll. Сам класс Thread относится только к члену m_Name в методах получения и установки 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});
}

Еще одна вещь, на которую следует обратить внимание, это то, что у вас могут возникнуть проблемы с безопасностью доступа к коду и изменением приватных членов в зависимости от того, какой уровень доверия у приложения. Очевидно, что этот тест, похоже, не вступил в игру, но стоит упомянуть здесь.

4 голосов
/ 28 июля 2010

Я бы не стал менять название темы так, как вы.Хотя вы делаете то же самое, что требуется для операции записи-записи, вы не знаете, что не существует поведения, зависящего от операции, выполняемой однократной записью.

Например, отладчик, если онполучает имя потока, может кэшировать это имя и больше не вызывать для него объект.

Здесь также возникает вопрос о том, почему вы полагаетесь на имя потока, чтобы помочь с регистрацией;вы полагаетесь на поведение регистратора, чтобы указать часть операции, которую вы пытаетесь записать в журнал.

Если вы хотите захватить определенную семантику, вам не следует формировать регистратор и поток, чтобы соответствоватьшаблон, который захватит эту семантику.Вместо этого явно укажите семантику для регистратора в конкретный момент времени.

2 голосов
/ 16 сентября 2013

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";
}
1 голос
/ 02 февраля 2015

Я поразил это сегодня, так же, как мой код собирался пойти в производство :-( и я не могу понять причину этого решения о дизайне.

Мое исправление заключалось в том, чтобы вставить имя потока в стек контекста log4net NDC и зарегистрировать его, используя шаблон %ndc. Если некоторые из ваших потоков не установят NDC, тогда этот ответ также полезен.

1 голос
/ 30 августа 2013

Возможный обходной путь - создание класса экземпляра, содержащего словарь пар идентификатора thead - имя-ключ.

Ваш регистратор нуждается в доработке, но можно вызвать словарь, чтобы вставить «имя» в оператор журнала.

У меня та же проблема, что и при использовании потоков пула потоков, которые просто используются повторно.

1 голос
/ 28 июля 2010

Имена потоков в .NET (и Java) используются исключительно для целей отладки и диагностики. Хотя логика того, что Java может переименовывать свои потоки, что .NET может делать то же самое, ошибочна (поскольку поток .NET является оберткой над системным потоком с дополнительными функциями, как и поток Java, но в остальном они не связаны) нет никакого вреда как такового в изменении имени потока, кроме риска поломки в будущих версиях, поскольку вы используете непубличный API.

Однако, по какой причине вы изменили его? Я думаю, что это было сделано только для чтения, чтобы избежать создания потоков «кухонной раковины», которые выполняют всевозможные задачи. Хотя есть исключения, конечно, я бы предостерег вас от рассмотрения, является ли дизайн, требующий этого, правильным дизайном.

0 голосов
/ 22 января 2014

Приведенный выше ответ от vinzbe оказался для меня полезным.Ответ от Brain Gideon У меня была проблема, что структура ThreadHandle требуется для InformThreadNameChange (.net 4.0).Таким образом, простое выполнение вышеизложенного не сообщит VS о том, что произошла смена имени, однако в моем включенном коде вы можете видеть, что после того, как вы установили для имени значение NULL, установите для имени протектора значение 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;
            }
        } 
    }
}
0 голосов
/ 28 июля 2010

Смена имени или попытка изменить имя вполне могут что-то сломать.Если реализация System.Threading.Thread изменяется таким образом, что поле m_Name называется m_ThreadName, например, в будущей версии .NET Framework или в пакете обновления или исправлении (хотя это вряд ли возможно), ваш код выдаст исключение.

0 голосов
/ 28 июля 2010

Существует определенная вероятность того, что что-то зависит от того, что имя является неизменным, что является обязательным условием дизайна.Нарушая эту инкапсуляцию, вы рискуете сломать что-то, что (добросовестно) полагается на это.Я не могу привести конкретный пример, но поскольку это свойство может использоваться любым мыслимым образом, проблема не ограничена.В качестве теоретического примера можно использовать имя потока в качестве ключа в коллекции.

Хотелось бы узнать, есть ли конкретный пример, хотя я тоже использую log4net и посмотрю, к чему вы клоните,:)

Обновление

Это, безусловно, пробудило мой интерес как пользователя log4net.Не желая поддерживать ветвь log4net, это возможное решение, которое является более безопасным.

  1. Написать обертку для log4net, то есть интерфейс типа ILog (у меня уже есть одна и 15 минут работы).

  2. Используйте технику локальной переменной потока, чтобы записать имя потока (например, с помощью метода расширения Thread.LoggingName = "бла-бла-бла") в точках входа в ваши компоненты.

  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...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...