В Java переименование распараллеливает, возможно. В.NET это не. Это вызвано тем, что Имя является свойством, которое является неперезаписываемым в классе Потока:
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#, если я избегаю определенного набора функциональности что a) я не забочусь или b) не использует вообще.
У Вас есть какая-либо идея, почему переименование Потока является неперезаписываемой операцией? Какая-либо идея, изменив имя повреждает что-то?
Я попробовал тест, где я переименовываю поток как таковой:
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) Поток использования. Имя для определения, который поток делает что действие.Заранее спасибо.
Править: Прекратите предлагать очевидные вещи! Я знаю, как назвать поток в запуске, если я спрашиваю, как Переименовать его.
Причина, почему я должен сделать это, состоит в том, что поток будет снова использован, и он может использоваться другим компонентом, и я хочу показать это, если и когда там будет регистрировать появление, чтобы иметь определенное имя потока, а не универсальное число.
Я использовал операцию анализа из 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});
}
Также следует отметить, что у вас могут возникнуть проблемы с безопасностью доступа к коду и изменением закрытых членов в зависимости от того, какой уровень доверия имеет приложение. Очевидно, это не повлияло на ваш тест, но об этом стоит упомянуть здесь.
Имена потоков в .NET (и Java) используются исключительно для целей отладки и диагностики. Хотя логика, что, поскольку Java может переименовывать свои потоки, .NET может делать то же самое, ошибочна (поскольку файл.NET-поток - это оболочка над системным потоком с дополнительной функциональностью, как и поток Java, но в остальном они не связаны), нет никакого вреда как такового в изменении имени потока, кроме риска поломки в будущем версий, так как вы используете непубличный API.
Однако по какой причине вы его меняете? Я думаю, что он был сделан только для чтения, чтобы избежать создания потоков "кухонной раковины", которые выполняют всевозможные задачи. Конечно, есть исключения, но я бы посоветовал вам подумать о том, является ли дизайн, который требует этого, правильным.
У потоков на уровне ОС нет имен. На самом деле, это просто удобство.
Существует определенная вероятность того, что что-то полагается на неизменность имени, что и требует дизайн. Нарушая эту инкапсуляцию, вы рискуете сломать то, что (добросовестно) на нее полагается. Я не могу привести конкретный пример, но поскольку это свойство можно использовать любым мыслимым образом, проблема безгранична. В качестве теоретического примера что-то может использовать имя потока в качестве ключа в коллекции.
Я хотел бы знать, есть ли офигительный конкретный пример, поскольку я тоже использую log4net и смотрю, к чему вы клоните. :)
Обновление
Это определенно вызвало у меня интерес как пользователя log4net. Не желая поддерживать вилку log4net, это более безопасное возможное решение.
Напишите оболочку для log4net, т.е. интерфейс типа ILog (у меня уже есть одна и 15 минут работы).
Используйте технику локальной переменной потока для записи имени потока (например, с помощью метода расширения Thread.LoggingName = "бла-бла-бла") в точках входа в ваши компоненты.
В оболочке журналирования временно измените имя потока, а затем снова измените его после входа в систему.
Это заботится о переименовании потоков и переименовании их снова, так что если что-то, что не называет потоки, выходит из системы, оно не выходит из системы с неправильным именем.
Обновление 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...
}
Изменение имени, или попытка изменить имя, вполне может что-то сломать. Если реализация System.Threading.Thread
изменится таким образом, что поле m_Name
будет называться m_ThreadName
, например, в будущей версии .NET Framework, или даже в пакете обновлений или исправлении (хотя это и маловероятно), ваш код выдаст исключение.
Я бы не стал менять название потока таким образом, как это делаете вы. Хотя вы делаете то же самое, что потребовала бы операция write-many, вы не знаете, что не существует поведения, зависящего от того, что операция write-once.
Например, отладчик, получив имя потока, может кэшировать это имя и больше не вызывать объект для его получения.
Здесь также возникает вопрос дизайна, почему вы зависите от имени потока, чтобы помочь с протоколированием; вы полагаетесь на поведение регистратора, чтобы указать часть операции, которую вы пытаетесь протоколировать.
Если вы хотите зафиксировать определенную семантику, вам не следует формировать регистратор и поток в соответствии с шаблоном, который позволит зафиксировать эту семантику. Вместо этого явно укажите семантику регистратору в конкретный момент времени.