The .NET Console class and its default TextWriter implementation (available as Console.Out and implicitly in e.g. Console.WriteLine()) does not signal any error when the application is having its output piped to another program, and the other program terminates or closes the pipe before the application has finished. This means that the application may run for longer than necessary, writing output into a black hole.
How can I detect the closing of the other end of the redirection pipe?
A more detailed explanation follows:
Here are a pair of example programs that demonstrate the problem. Produce prints lots of integers fairly slowly, to simulate the effect of computation:
using System; class Produce { static void Main() { for (int i = 0; i < 10000; ++i) { System.Threading.Thread.Sleep(100); // added for effect Console.WriteLine(i); } } }
Consume only reads the first 10 lines of input and then exits:
using System; class Consume { static void Main() { for (int i = 0; i < 10; ++i) Console.ReadLine(); } }
If these two programs are compiled, and the output of the first piped to the second, like so:
Produce | Consume
… it can be observed that Produce keeps on running long after Consume has terminated.
In reality, my Consume program is Unix-style head, and my Produce program prints data which is costly to calculate. I’d like to terminate output when the other end of the pipe has closed the connection.
How can I do this in .NET?
(I know that an obvious alternative is to pass a command-line argument to limit output, and indeed that’s what I’m currently doing, but I’d still like to know how to do this since I want to be able to make more configurable judgements about when to terminate reading; e.g. piping through grep before head.)
UPDATE: It looks horribly like the System.IO.__ConsoleStream implementation in .NET is hard-coded to ignore errors 0x6D (ERROR_BROKEN_PIPE) and 0xE8 (ERROR_NO_DATA). That probably means I need to reimplement the console stream. Sigh…)
To solve this one, I had to write my own basic stream implementation over Win32 file handles. This wasn’t terribly difficult, as I didn’t need to implement asynchronous support, buffering or seeking.
Unfortunately, unsafe code needs to be used, but that generally isn’t a problem for console applications that will be run locally and with full trust.
Here’s the core stream:
BufferedStreamcan be wrapped around it for buffering if needed, but for console output, theTextWriterwill be doing character-level buffering anyway, and only flushing on newlines.The stream abuses
Win32Exceptionto extract an error message, rather than callingFormatMessageitself.Building on this stream, I was able to write a simple wrapper for console I/O:
The final result is that writing to the console output after the other end of the pipe has terminated the connection, results in a nice exception with the message:
By catching and ignoring the
IOExceptionat the outermost level, it looks like I’m good to go.