如何确保获取和设置操作的原子性以重定向 Console.Out 以记录控制台输出?

发布于 2024-07-10 02:49:13 字数 921 浏览 6 评论 0原文

我需要拦截控制台输出流,以便捕获它以记录日志,但仍将内容传递到原始流,以便应用程序正常工作。 这显然意味着在使用 Console.SetOut(new MyTextWriterClass(originalOut)) 更改之前存储原始的 Console.Out TextWriter。

我假设获取 Out 属性和调用 SetOut() 方法的各个操作是由 Console 以线程安全的方式实现的。 但我想确保其他一些线程(例如,运行我无法控制且无法期望更改的客户端应用程序代码,因此我不能依赖我自己的自定义锁定方案)不能在我的获取和设置之间意外地更改它,最终被我对它的更改覆盖(破坏了他们的应用程序的行为!)。 由于其他代码可能只是调用 SetOut(),因此我的代码理想情况下应该获得 Console 内部使用的相同锁(假设有一个)。

不幸的是,Console 是一个(静态)类,而不是实例,因此您不能只是锁定(Console)。 并且查看类文档似乎没有提到锁定。 这不是这些控制台方法的通常预期用法,但应该有一些安全的方法将其作为原子操作来执行。

如果标准锁定方案失败,是否有其他方法可以确保这一点? 对于如此短的关键部分(并且仅执行一次),即使暂时阻塞所有其他线程也是可以接受的,如果这是唯一的方法的话。 我们使用 C# 和 .NET2.0。

如果甚至不可能(在不中断客户端应用程序的情况下),那么我们就只能依赖客户端应用程序不太可能重定向其控制台输出 碰巧在 get 和 set 操作之间执行此操作。 我只是想涵盖所有基础,以防万一。

编辑:现在我们有了示例代码的具体答案,我已经重新措辞了问题标题,以更普遍地反映答案可以提供帮助的用例,从而更加清晰。 另外,添加了“原子”标签。

I need to intercept the console output stream(s) in order to capture it for a log but still pass things through to the original stream so the application works properly. This obviously means storing the original Console.Out TextWriter before changing it with Console.SetOut(new MyTextWriterClass(originalOut)).

I assume the individual operations to get the Out property and to call the SetOut() method are implemented by Console in a thread-safe manner. But I'd like to make sure that some other thread (eg. running client application code that I don't control and can't expect to change, and so I can't rely on my own custom locking scheme) can't accidentally change it in between my get and set and end up getting overwritten by my change to it (breaking their application's behavior!). Since the other code may simply call SetOut(), my code should ideally get the same lock used internally by Console (assuming there is one).

Unfortunately, Console is a (static) class, not an instance, so you can't just lock (Console). And looking in the class documentation there does not seem to be any mention of locking. This is not the normally-expected usage of these Console methods, but there should be some safe way of doing this as an atomic operation.

Failing a standard locking scheme, is there some other way to ensure this? For such a short critical section (and done only once), even momentarily blocking all other threads might be acceptable, if that's the only way to do it. We're using C# and .NET2.0.

If not even that is possible (without disrupting the client application), then we'll just have to rely on it being very unlikely that the client application would redirect its console output and happen to do it in between our get and set operations. I'd just like to cover all the bases, just in case.

Edit: Now that we have a concrete answer with example code, I've reworded the question title to more generally reflect the use cases where the answer(s) can help, to be more clear. Also, added a tag for "atomic".

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

携余温的黄昏 2024-07-17 02:49:13

如果您查看 SetOut 的实现,它对我来说看起来是线程安全的:

[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;
    }
}

编辑

我能想到的最接近的解决方案是使用反射来获取其InternalSyncObject,然后锁定它。

需要注意的是,这是一个非常糟糕的主意,只有在没有其他选择的情况下才应该使用。 您可能会导致框架出现异常行为并使进程崩溃。

您还需要注意任何服务包和主要版本,以确保内部变量仍然被使用。 由于它是内部的,因此无法保证它会出现在下一个版本中。 如果您不是具有反射的对象,请编写您的代码并尝试很好地降低用户体验。

祝你好运:-)

If you look at the implementation for SetOut it looks thread safe to me:

[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;
    }
}

Edit

The cloest thing to a solution I could come up with is using reflection to get their InternalSyncObject and then lock on it.

A word of caution, this is an extremly bad idea and should only be used when no other option exists. You could cause the framework to behave unexpectadly and crash the process.

You will also need to pay attention to any service packs and major releases making sure the internal variable is still used. Since its internal there is no promise that it will be there in the next release. Write your code defensivley and try and degrade the user experience nicely should you not the object with reflection.

Good luck:-)

星星的轨迹 2024-07-17 02:49:13

好的,现在我有一些时间来实际实施 Josh 建议的反射方法。 基本代码(在我的 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();
    }
}

类似于:

private static void DoIntercepterRegistration()
{
    Console.SetOut(new ConsoleIntercepter(Console.Out));
    Console.SetError(new ConsoleIntercepter(Console.Error));
}

然后 DoIntercepterRegistration()将 Out 和 Error 的受锁保护的原子替换; 显然,实际的拦截、处理和传递给前面的 TextWriter 需要额外的代码,但这不是这个问题的一部分。

请注意(在为.NET2.0提供的源代码中)Console类使用InternalSyncObject来保护Out和Error的初始化并保护SetOut()和SetError(),但它不使用Out和Error的读取周围的锁一旦它们之前被初始化。 这对于我的特定情况来说已经足够了,但如果你做了更复杂(和疯狂)的事情,可能会违反原子性; 我想不出任何有用的场景来解决这个问题,但可以想象故意病态的场景。

Okay, now I've had some time to actually work up an implementation of the reflection approach Josh suggested. The basic code (in my ConsoleIntercepter class, which inherits from TextWriter) is:

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

Then the DoIntercepterRegistration() would be something like:

private static void DoIntercepterRegistration()
{
    Console.SetOut(new ConsoleIntercepter(Console.Out));
    Console.SetError(new ConsoleIntercepter(Console.Error));
}

That's just for the lock-protected atomic replacement of Out and Error; obviously the actual interception, handling, and passing on to the previous TextWriters requires additional code, but that's not part of this question.

Note that (in the source code provided for .NET2.0) the Console class uses InternalSyncObject to protect initialization of Out and Error and to protect SetOut() and SetError(), but it does not use the lock around reads of Out and Error once they have been previously initialized. This is sufficient for my particular case, but could have atomicitiy violations if you did something more complex (and crazy); I can't think of any useful scenarios with that problem, but deliberately pathological ones can be imagined.

风吹雨成花 2024-07-17 02:49:13

如果您可以在 Main() 中尽早执行此操作,那么您将有更好的机会避免任何竞争条件,特别是如果您可以在创建工作线程之前执行此操作。 或者在某个类的静态构造函数中。

如果 Main() 标记有 [STAThread] 属性,它将在单线程单元中运行,因此这也应该有所帮助。

If you can do this early in Main() you'll have a much better chance of avoiding any race conditions, especially if you can do it before something creates a worker thread. Or in the static constructor of some class.

If Main() is marked with the [STAThread] attribute it will be running in a single threaded apartment so that should help too.

滥情空心 2024-07-17 02:49:13

一般来说,我会使用安全模型来防止客户端代码在您不注意时重定向控制台。 我确信这需要完全信任,因此如果客户端代码在部分信任的情况下运行,它将无法调用 SetOut()。

In general, I would use the security model to keep client code from redirecting the Console while you're not looking. I'm sure that requires Full Trust, so if client code is running with Partial trust, it won't be able to call SetOut().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文