Переименование темы
В 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 разрешает переименование потоков и большинство используемых структур нитей, поставляемых ОС на обеих платформах, я склонен думать, что могу фактически переименовать поток на С#, если я избегу определенного набора функциональность, которая: а) мне все равно или б) вообще не использовать.
Есть ли у вас какие-либо идеи, почему переименование потоков является операцией однократной записи? Любая идея, если изменение названия что-то сломает?
Я пробовал тест, где я переименовал поток как таковой:
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
Я использовал операцию анализа из Reflector и единственный код в BCL, который я видел (точнее, Nikolaos), который использует геттер Thread.Name
был вызовом API RegisterClassEx
в user32.dll. Сам класс Thread
относится только к элементу m_Name
в getter и setter 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});
}
Еще одно замечание: у вас могут быть проблемы с безопасностью доступа к коду и смены частных членов в зависимости от того, какой уровень доверия у приложения. Очевидно, что это, похоже, не входило в игру с вашим тестом, но стоит упомянуть здесь.
Ответ 2
Темы, на уровне ОС, не имеют имен. Действительно, это просто удобная функция.
Ответ 3
Я бы не стал менять название потока так, как вы. В то время как вы делаете то же самое, что требует операция записи-много, вы не знаете, что нет действий, зависящих от того, что операция является однократной записью.
Например, отладчик, если он получает имя потока, может кэшировать это имя и не должен вызывать объект для него больше.
Здесь также возникает вопрос о дизайне, почему вы зависите от имени потока, которое поможет в вашем протоколировании; вы полагаетесь на поведение регистратора, чтобы указать часть операции, которую вы пытаетесь выполнить.
Если вы хотите захватить определенную семантику, вам не следует формировать логгер и поток, чтобы они соответствовали шаблону, который будет захватывать эту семантику. Вместо этого явным образом указываю семантику регистратору в определенный момент времени.
Ответ 4
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
Названия потоков в .NET(и Java) используются исключительно для целей отладки и диагностики. Хотя логика, что, поскольку Java может переименовать свои потоки, что .NET может сделать то же самое, является ошибочной (поскольку поток .NET является оболочкой над системным потоком с дополнительными функциями, как и Java-поток, но они в противном случае не связаны), нет никакого вреда как такового при изменении имени потока, кроме риска поломки в будущих версиях, поскольку вы используете непубличный API.
Однако, какая у вас причина для его изменения? Я думаю, что это было сделано только для чтения, чтобы избежать создания потоков "кухонных раковин", которые выполняют всевозможные задачи. Конечно, есть исключения, я бы посоветовал вам подумать над тем, нужен ли дизайн, требующий этого, правильного дизайна.
Ответ 6
Возможная работа вокруг будет состоять в том, чтобы иметь класс экземпляра, содержащий словарь из пары значений id ID - "name".
Ваш регистратор нуждается в переделке, но словарь можно вызвать, чтобы вставить "имя" в оператор журнала.
У меня такая же проблема, поскольку я использую потоки пула потоков, которые просто используются повторно.
Ответ 7
Я ударил этого сегодня, точно так же, как мой код собирался выйти на производство:-( и я не могу понять причину этого дизайнерского решения.
Мое исправление заключалось в том, чтобы направить имя потока в log4net NDC
стек контекста и занести его в журнал с помощью шаблона %ndc
. Если некоторые из ваших потоков не будут устанавливать NDC, тогда этот ответ также будет полезен.
Ответ 8
Существует определенная возможность того, что что-то полагается на неизменное имя, что и требует мандат. Разрушая эту инкапсуляцию, вы рискуете сломать что-то, что (добросовестно) полагается на это. Я не могу привести конкретный пример, но поскольку это свойство можно использовать любым возможным способом, проблема неограничена. В качестве теоретического примера что-то может использовать имя потока в качестве ключа в коллекции.
Мне бы хотелось знать, был ли конкретный пример с капелькой, хотя я тоже использую log4net и вижу, во что вы едете.:)
Обновление
Это, безусловно, вызвало мой интерес как пользователя log4net. Не желая поддерживать вилку log4net, это возможное решение, которое безопаснее.
-
Напишите оболочку для log4net, то есть интерфейс типа ILog (у меня уже есть одна и 15 минут работы).
-
Используйте метод локальной локальной переменной для записи имени потока (например, с помощью метода расширения Thread.LoggingName = "blah blah blah" ) в точках входа в ваши компоненты.
-
В вашей логгеровой обертке временно измените имя потока и затем снова измените его после ведения журнала.
Это позаботится о том, чтобы переименовать потоки и обработать повторное присвоение им имени снова, так что если что-то, что не выдает нити, выйдет из системы, оно не выйдет с неправильным именем.
Обновление 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...
}
Ответ 9
Изменение имени или попытка изменить имя может сильно нарушить что-то. Если реализация System.Threading.Thread
изменяется так, что поле m_Name
называется m_ThreadName
, например, в будущей версии .NET Framework или действительно пакете обновления или исправлении (маловероятно, хотя это может быть), ваш код выдаст исключение.
Ответ 10
Вышеупомянутый ответ 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;
}
}
}
}