2010-07-28 2 views
14

В Java возможно переименование потоков. В .NET это не так. Это происходит потому, что имя это свойство, которое является однократной записи в классе Thread:Thread Renaming

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, чтобы указать, какой поток выполняет какое действие. Заранее спасибо.

EDIT: Пожалуйста, прекратите предлагать очевидные вещи! Я знаю, как назвать поток при запуске, если я спрошу, как RE-name его.

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

+1

Почему вы хотите переименовать -й читать? Кроме того, что Java может это сделать? – ChrisF

+1

@ChrisF: см. Править выше. –

+0

Достаточно справедливо, хотя вы можете получить 'LoggingThread' из' Thread', который предоставляет свойство read/write, называемое 'ThreadName', и использовать его вместо этого. Я знаю, что это дополнительное свойство, имеющее такое же значение, но оно делает то, что вы хотите, и не полагается на отражение, которое может сломаться. – ChrisF

ответ

6

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

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

+1

Я также запустил анализ, и он показал мне, что MS.Win32.HwndWrapper..ctor (...) использует Get.Name getter. Он использует его для установки «string lpszClassName» нового окна ... – Nikolaos

+0

@Nikolaos: Да, приятно поймать. Я не уверен, как я пропустил это в первый раз. –

+0

InformThreadNameChangeEx() недоступен в .NET framework 4.0. См. Http://stackoverflow.com/a/18823380/1418147 ниже. – John

1

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

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

+0

Да, им лучше добавить нить-локальную метку или что-то еще. –

+0

@Adam: Это не мое дизайнерское решение, log4net использует Thread.Name для регистрации имени текущего потока. Поэтому, если я хочу записать значимое имя, мне нужно переименовать поток, так как потоки повторно используются в разных компонентах, и я хочу обозначить, какой из них. –

+0

@_NT: log4net является открытым исходным кодом. Если это что-то неудобно, исправьте. –

10

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

+0

Строго говоря, вы правы, но вы можете связать с ним имя через SEH: см. Https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx – GreatAndPowerfulOz

0

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

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

Update

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

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

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

  3. В вашей логгеровой обертке временно измените имя потока и затем снова измените его после ведения журнала.

Это заботится о повторном именовании потоков и обрабатывает повторно называя их обратно, так что если что-то, что не именует тему выходит из него не выйти с неправильным именем.

Update 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... 
} 
+0

Я понимаю, что это так же ясно, как вы получить от программистов, работающих над System.Threading, что именование, если (в соответствии с их пониманием) очень важно. Но интересно, кто-нибудь знает, что от этого зависит? (Моно разработчики, которые я смотрю на вас) –

+0

@_NT Это не просто фреймворк, о котором я думаю. Это общедоступное свойство, поэтому все может зависеть от него, например. другие рамки и инструменты, которые вы используете. –

+0

Очень интересное обновление. Можете ли вы подробно остановиться на пункте 2, что такое технология локальной переменной потока, которую вы имеете в виду. А в 3, вы имеете в виду, изменив только поле поддержки «m_Name» (т. Е. Не вызывая метод собственных уведомлений) и затем вернув его обратно? Последнее звучит достаточно безобидно, хотя и зависит от имени поля. –

4

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

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

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

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

+0

Мне ясно (и я попытался объяснить это в вопросе выше), что это показатель от программистов, работающих над System.Threading, что это лучше не подделывать. Но кто-нибудь точно знает, что от этого зависит? –

+0

@_NT: Это похоже на вопрос, что зависит от GetType(). Это общедоступное свойство типа, почти невозможно сказать, что от него зависит или какие предположения сделаны из-за того, что он один раз. – casperOne

+0

См. Ответ Брайана Гидеона! –

0

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

+1

На самом деле, но я не планирую использовать вышеизложенное, я просто проверяю, работает ли это, невзирая на что-то сразу: он работает на этом -ВЕР-экспериментальном уровне. –

1

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

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

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

2

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

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

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

Моего исправления было нажать имя нити в log4net NDC context stack & зарегистрировать ее с помощью %ndc рисунка. Если некоторые из ваших потоков не будут устанавливать NDC тогда this answer также полезен.