代码之家  ›  专栏  ›  技术社区  ›  Rob Parker

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

  •  3
  • Rob Parker  · 技术社区  · 16 年前

    我需要截取控制台输出流,以便将其捕获为日志,但仍要将内容传递到原始流,以便应用程序正常工作。这显然意味着存储原始文件 Console.Out Console.SetOut(new MyTextWriterClass(originalOut))

    Console 安慰

    不幸地 安慰 lock (Console) . 在类文档中,似乎没有提到锁定。这不是这些控制台方法通常预期的用法,但是应该有一些安全的方法作为原子操作来实现这一点。

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

    编辑

    4 回复  |  直到 16 年前
        1
  •  2
  •   JoshBerke    16 年前

    如果您查看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,然后锁定它。

    您还需要注意任何service Pack和主要版本,确保内部变量仍在使用。由于它的内部版本,因此没有承诺它将在下一版本中出现。编写代码Defensively并尝试很好地降低用户体验,如果您不使用反射对象的话。

    祝你好运:-)

        2
  •  1
  •   Rob Parker    16 年前

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

    文字撰稿人

    请注意(在为.NET2.0提供的源代码中),Console类使用InternalSyncObject来保护Out和Error的初始化,并保护SetOut()和SetError(),但它不会在Out和Error的读取之前初始化后使用锁。这对于我的特殊情况已经足够了,但是如果你做了更复杂(和疯狂)的事情,可能会违反原子性;我想不出任何关于这个问题的有用方案,但可以想象得到。

        3
  •  0
  •   devstuff    16 年前

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

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

        4
  •  0
  •   Sean Reilly    16 年前

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

    推荐文章