重定向到标准输出的异步写入是同步还是异步的?

6

Console 的文档说明

若要重定向标准输入、标准输出或标准错误流,分别调用 Console.SetIn、Console.SetOut 或 Console.SetError 方法。使用这些流的 I/O 操作是同步的,这意味着多个线程可以从这些流中读取或写入数据。这意味着通常异步执行的方法(例如 TextReader.ReadLineAsync)如果对象表示控制台流,则会同步执行。

这是否意味着即使将输出重定向到文件,此代码也会以同步方式执行?

Console.Out.WriteLineAsync("Hello World Async");

实际应用场景是:如果我的程序将日志输出到 stdout,并以重定向到文件或管道到另一个程序的方式进行调用,如果操作系统或管道接收方没有及时消耗缓冲区,那么我的程序会停顿吗?

或者,有人能想出一种确定的方法吗?


3
将输出重定向到一个不读取标准输入的程序中,看看会发生什么。 - Servy
3
我不太确定我该如何做...但是我会尝试一下。 - Anders Sewerin Johansen
3
请创建一个什么都不做、永久阻塞的程序,并将输出重定向到该程序。然后你就会看到当输出缓冲区没有被消耗时会发生什么事情。 - Servy
1个回答

1
如果我的程序将日志输出到标准输出(stdout),并且以重定向文件或管道到另一个程序的方式调用,如果缓冲区没有及时被操作系统或管道接收方消耗,我的程序会停顿吗?
是的,会停顿。
当你调用Console.SetOut()时,以下代码将被执行:
public static void SetOut(TextWriter newOut) {
    if (newOut == null)
        throw new ArgumentNullException("newOut");
    Contract.EndContractBlock();
#pragma warning disable 618
    new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
#pragma warning restore 618
#if FEATURE_CODEPAGES_FILE    // if no codepages file then we are locked into default codepage  and this field is not used              
    _isOutTextWriterRedirected = true;
#endif
    newOut = TextWriter.Synchronized(newOut);
    lock(InternalSyncObject) {
        _out = newOut;
    }
}

即,新的输出TextWriter始终是由TextWriter.Syncronized()返回的一个写入器。这反过来总是返回一个SyncTextWriter对象。如果您查看该类的WriteLineAsync()方法,它会执行两个操作:
  • 它本身是一个同步方法,这意味着如果多个线程尝试使用该写入器,则实际上只有一个线程会使用它(即所有带有该注释的方法在线程之间是互斥的)。
  • 更重要的是,WriteLineAsync()方法调用底层TextWriter上的同步WriteLine()方法,该方法用于创建SyncTextWriter对象。
那第二点的意思是该方法在操作完成前不会返回。它总是返回一个已完成的任务。它没有办法返回一个未完成的任务,如果操作因任何原因被阻塞,例如输出流的使用者没有消耗它,从而导致输出缓冲区被填满,调用将不会返回。
这将导致您进行此类调用的线程被阻塞,直到写操作可以完成,即最初导致其阻塞的任何条件都已解决。
现在,所有这些都说完了:
  • 如果你正在写入文件,无论是因为调用了SetOut()还是因为进程的标准输出已被重定向,这可能会使程序变慢,具体取决于程序生成的输出量相对于其他工作的多少,但我不认为你会遇到任何被称为“停顿”的情况。即磁盘I/O很慢,但通常不会阻塞很长时间。
  • 如果调用SetOut()并传递其他类型的writer,假设您对其具有控制权,并且可以确保正确编写并在编写时不会阻塞。
  • 如果stdout外部被重定向到其他目标,例如启动了您的进程并重定向了Process.StandardOutput流的第二个.NET程序,则该程序应确保跟上,如果它关心您的程序能够继续无障碍地进行。但无论如何都是这样的;进程输出流的使用者应始终确保尽快读取它们。
我个人认为程序本身不应该保护自己免受可能会阻塞的重定向流的影响,除非当然不将控制台流内部重定向到可能的目标对象。通常情况下,这被认为是接收进程的工作,因为它是唯一关心重定向程序是否能够完成其工作的进程。
如果真的有问题,你可以在进程内部缓冲输出,例如使用一个BlockingCollection<string>对象来协调想要写入输出的线程和实际执行写入操作的消费线程。但这并不是很有用,因为这只是把问题推迟了,如果不能指望消费线程能够不受阻碍地继续进行,你不希望无限期地写入集合。如果消费者真的被阻塞,集合将变得太大,并在某些时候抛出OutOfMemoryException。您将遇到与任何其他机制类似的问题,该机制旨在将写入委托给不同的线程,以便让当前线程在不受阻塞的情况下运行。
在我看来,最好只是正常地编写代码(即,甚至不要使用...Async方法,除非您的代码将编写抽象为TextWriter并且它旨在在支持异步时异步工作),并依靠流的消费端“做正确的事情”,而不会阻塞您的进程。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接