0

I was using this code to redirect my console output to file and then read and display it. I want to go away from using files because I'm polluting my folders with those console files. How can I do this in memory ? I don't want any files to pollute the system. Maybe I'm trying something weird here. I just want 1 thread to read the console output of the very same application:

  • 1 application
  • multiple threads write to console
  • 1 thread reads from console

My working file code:

private StreamWriter currentOut = null;

private void RedirectConsole()
{
    currentOut = new StreamWriter(new FileStream(filename,
        FileMode.Create, FileAccess.Write, FileShare.Read));
    currentOut.AutoFlush = true;
    Console.SetOut(currentOut);
    ThreadPool.QueueUserWorkItem(o => { Listen(); });
}

private void Listen()
{
    StreamReader fileIn = new StreamReader(new FileStream(filename,
        FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
    while (true)
    {
        try
        {
            if (!fileIn.EndOfStream)
            {
                string a = fileIn.ReadLine();
                MessageBox.Show(a);
            }
            Thread.Sleep(25);
        }
        catch { }
    }
}

This seems to be what I want. But I'm unable to implement that (help?). File is like a buffer. You write to it from one end and read from another. I need the same in memory.

Community
  • 1
  • 1
Bitterblue
  • 13,162
  • 17
  • 86
  • 124
  • Were you creating a new file every time? Could you just use one file and clear it out when the program closes or something? I dunno about redirecting it to another thread, but it seems like you at least shouldn't need to "pollute" your folders. – eddie_cat Jul 31 '14 at 15:44
  • @eddie_cat Yes I was opening a new file every start of the app. For multi-instances I chose a new filename each time. And when for example the app crashes the file will remain. I think it would be a bad solution to be looking for files to remove or whatever. – Bitterblue Jul 31 '14 at 15:48
  • If you want the file to remain if the app crashes, your only option may be to write it to a file. – Sarima Jul 31 '14 at 15:49
  • @Joshpbarron No, I don't want any files. – Bitterblue Jul 31 '14 at 15:53

3 Answers3

2

Try:

private StreamWriter currentOut = null;
private MemoryStream ms = new MemoryStream();

private void RedirectConsole()
{
    currentOut = new StreamWriter(ms);
    currentOut.AutoFlush = true;
    Console.SetOut(currentOut);
    ThreadPool.QueueUserWorkItem(o => { Listen(); });
}

private void Listen()
{
    StreamReader fileIn = new StreamReader(ms);
    // ...
}
kidshaw
  • 3,423
  • 2
  • 16
  • 28
  • I tried that, too. But I don't get anything on my reader thread. Maybe a buffer is missing or something ? – Bitterblue Jul 31 '14 at 16:00
  • Didn't work for me either. Dunno why. Did you try it actually ? [Here](http://stackoverflow.com/a/25076159/1442225) is my solution that works exactly how I wanted. – Bitterblue Aug 01 '14 at 08:42
  • This is a strip down of something I have done in the past that worked. I'm glad you've got to a working solution. – kidshaw Aug 01 '14 at 12:05
1

The problem with using MemoryStream is that the read position advances with the write position. Pipes (System.IO.Pipes namespace) are a better choice for use as temporary buffers where the read position needs to advance independent of the write position. Admittedly, this more or less does exactly what your working solution does, though it removes the need to implement the buffer yourself.

class ConsoleRedirector : IDisposable
{
    private TextWriter originalOut = Console.Out;
    private AnonymousPipeServerStream consoleOutServerPipe;
    private StreamWriter currentOut;

    public ConsoleRedirector()
    {
        this.consoleOutServerPipe = new AnonymousPipeServerStream(PipeDirection.Out);
        this.currentOut = new StreamWriter(this.consoleOutServerPipe);
        this.currentOut.AutoFlush = true;
        Console.SetOut(this.currentOut);
        ThreadPool.QueueUserWorkItem(o => { this.Listen(); });
    }

    private void Listen()
    {
        AnonymousPipeClientStream consoleOutClientPipe = new AnonymousPipeClientStream(PipeDirection.In, this.consoleOutServerPipe.ClientSafePipeHandle);
        using (StreamReader fileIn = new StreamReader(consoleOutClientPipe))
        {
            // ...
        }
    }

    public void Dispose()
    {
        this.currentOut.Dispose();
        Console.SetOut(this.originalOut);
    }
}
Mike Ness
  • 286
  • 2
  • 4
  • FYI, it looks like the System.IO.Pipes namespace was added in .NET 3.5, so this won't work if you're constrained to .NET 3.0 or below. – Mike Ness Nov 24 '14 at 23:28
0

I ended up writing a derived stream class and replaced the FileStream with my own stream. I probably should have avoided that. But since I couldn't find a working solution, it was also a good practice. Something like this:

public class MyStream: Stream
{
    private byte[] internalBuffer = new byte[4096];

    // ...

    public override int Read(byte[] buffer, int offset, int count)
    {
        // used by StreamReader
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        // used by StreamWriter
    }
}

override all the other stuff, handle multi-threading while enlarging internalBuffer and disposing passed data.

Bitterblue
  • 13,162
  • 17
  • 86
  • 124