我怎样才能确保获取和设置操作原子重定向Console.Out用于记录控制台输出?
-
21-08-2019 - |
题
我需要以捕获它为日志,但仍通过对原始流传递的东西,以便应用程序正常工作拦截控制台输出流(多个)。这显然意味着Console.Out
改变它之前存储原始Console.SetOut(new MyTextWriterClass(originalOut))
的TextWriter。
我假定的各个操作,以获得输出属性和调用放样()方法由Console
在一个线程安全的方式来实现。但我想,以确保其他线程(如运行的客户端应用程序的代码,我不控制,不能指望改变,所以我不能靠自己的自定义锁定方案)不能无意中改变它在我的GET之间并设置,最终通过我的改变它覆盖掉了(打破了他们的应用程序的行为!)。由于其它代码可以简单地调用放样(),我的代码应当理想地得到由Console
内部使用的相同的锁(假定存在)。
不幸的是,Console
是(静态)类,而不是一个实例,因此你不能只是lock (Console)
。而综观类文档中似乎没有被锁定的任何提及。这是不是这些控制台方法常预期使用,但应该有这样作为一个原子操作的一些安全的方式。
失败标准锁定方案,有一些其他的方式,以确保此?对于这么短的关键部分(与只进行一次),甚至暂时阻止所有其他线程是可以接受的,如果是这样做的唯一途径。我们使用C#和.NET2.0。
如果不连的是的可能(不中断客户端应用程序),那么我们就不得不依赖于它是不太可能的客户端应用程序将其重定向控制台输出和发生在我们这个get和set操作之间。我只是想涵盖所有的基础,以防万一。
修改:现在,我们有示例代码一个具体的答案,我改写的问题标题更普遍反映使用情况下的答案(S)可以帮助,更清晰。另外,添加了标签为“原子”。
解决方案
如果你看一下专列实施它看起来线程安全的对我说:
[HostProtection(SecurityAction.LinkDemand, UI=true)]
public static void SetOut(TextWriter newOut)
{
if (newOut == null)
{
throw new ArgumentNullException("newOut");
}
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
_wasOutRedirected = true;
newOut = TextWriter.Synchronized(newOut);
lock (InternalSyncObject)
{
_out = newOut;
}
}
修改
在cloest事情解决我能想出使用反射来获取他们的InternalSyncObject,然后将其锁定。
提醒一句,这是一个坏的极端想法并且当没有其它选项存在应该只被使用。您可能会导致框架unexpectadly行为和崩溃的过程。
您还需要注意什么服务包和主要版本,确保内部变量仍在使用。由于其内部没有任何的承诺,这将是那里的下一个版本。 defensivley编写代码,并尝试与反射很好地降低用户体验应该不是你的对象。
好运: - )
其他提示
好了,现在我已经有一段时间了实际工作起来反射方法乔希提出的实现。基本码(在我ConsoleIntercepter
类,它从TextWriter
继承)是:
private static object GetConsoleLockObject()
{
object lockObject;
try
{
const BindingFlags bindingFlags = BindingFlags.GetProperty |
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
// It's currently private, but we'd be happy if it were public, too.
Type consoleType = typeof(Console);
lockObject = consoleType.InvokeMember("InternalSyncObject", bindingFlags,
null, null, null);
}
catch
{
lockObject = null; // Return null on any failure.
}
return lockObject;
}
public static void RegisterConsoleIntercepter()
{
object lockObject = GetConsoleLockObject();
if (lockObject != null)
{
// Great! We can make sure any other changes happen before we read
// or after we've written, making this an atomic replacement operation.
lock (lockObject)
{
DoIntercepterRegistration();
}
}
else
{
// Couldn't get the lock object, but we still need to work, so
// just do it without an outer lock, and keep your fingers crossed.
DoIntercepterRegistration();
}
}
然后DoIntercepterRegistration()
会是这样的:
private static void DoIntercepterRegistration()
{
Console.SetOut(new ConsoleIntercepter(Console.Out));
Console.SetError(new ConsoleIntercepter(Console.Error));
}
这只是用于锁保护的原子置换出和误差;显然实际的拦截,处理和传递到以前TextWriter
s需要额外的代码,但是这不是这个问题的一部分。
请注意(在设置用于.NET2.0的源代码)控制台类使用InternalSyncObject保护Out和错误的初始化,并保护放样()和SETERROR(),但它不使用锁周围的读取输出和错误一旦他们此前已初始化。这是足以让我的具体情况,但可能有atomicitiy违规行为,如果你做了一些更复杂的(疯狂);我不能认为这一问题的任何有用的方案,但故意病理那些可想而知。
如果您在Main()
做到这一点早你就必须避免任何竞争条件的一个更好的机会,特别是如果事情会创建一个工作线程之前,你可以做到这一点。或在某些类的静态构造函数。
如果Main()
标有属性将在单线程公寓运行,使得应该帮助太[STAThread]
。
在一般情况下,我会使用的安全模式,以保持客户端代码从重定向控制台,而你不知道的。我敢肯定,需要完全信任,所以如果客户端代码与部分信任运行时,它将无法调用放样()。