Search code examples
c#.netmultithreadingfilestreamstreamreader

C# concurrent filestream read/write eof


I have a thread producing lines in a log file:

var t1 = Task.Factory.StartNew(() =>
{
    using (var fileStream = File.Open(file, FileMode.Create, FileAccess.Write, FileShare.Read))
    using (var streamWriter = new StreamWriter(fileStream))
    {
        for (var i = 0; i < 10; i++)
        {
            streamWriter.WriteLine(i);
            streamWriter.Flush();
            Thread.Sleep(1000);
        }
    }

    File.Delete(file);
});

And I have a thread reading lines from the same log file:

// Reads lines from the log file.
var t2 = Task.Factory.StartNew(() =>
{
    Thread.Sleep(500); // Horrible wait to ensure file existence in this test case.
    using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read | FileShare.Write))
    using (var streamReader = new StreamReader(fileStream))
    {
        string line;
        while ((line = streamReader.ReadLine()) != null) Console.WriteLine(line);
        // FIXME: The stream reader stops, instead of doing a continous read.
        Console.WriteLine("End of file");
    }
});

The reader is supposed to read all written lines, therefore it should wait for more data instead of stopping at the first time it encounters EOF. I do not mind if the reader is never 'finished', so long as the file continues to exist, the reader is allowed to continue reading. How can I achieve this? Full code for reproduction purposes:

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace PlayGround
{
    internal static class Program
    {
        private static void Main()
        {
            const string file = "test.log";

            // Writes lines to the log file.
            var t1 = Task.Factory.StartNew(() =>
            {
                using (var fileStream = File.Open(file, FileMode.Create, FileAccess.Write, FileShare.Read))
                using (var streamWriter = new StreamWriter(fileStream))
                {
                    for (var i = 0; i < 10; i++)
                    {
                        streamWriter.WriteLine(i);
                        streamWriter.Flush();
                        Thread.Sleep(1000);
                    }
                }

                File.Delete(file);
            });

            // Reads lines from the log file.
            var t2 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(500); // Horrible wait to ensure file existence in this test case.
                using (
                    var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read,
                        FileShare.Delete | FileShare.Read | FileShare.Write))
                using (var streamReader = new StreamReader(fileStream))
                {
                    string line;
                    while ((line = streamReader.ReadLine()) != null) Console.WriteLine(line);
                    // FIXME: The stream reader stops, instead of doing a continous read.
                    Console.WriteLine("End of file");
                }
            });

            Task.WaitAll(t1, t2);
        }
    }
}

EDIT: As a practical example, this is useful for a scenario where a third party process is producing log entries which need to be read and processed. You could see this as a log file reader if that makes the application and use clearer.


Solution

  • You could perform a wait when the line == null, by checking the streamReader.EndOfFile property. Using Thread.Sleep(1000) is not an ideal solution, a bit hacky, and I guess there are other better alternative solutions out there. :-)

    using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read | FileShare.Write))
    using (var streamReader = new StreamReader(fileStream))
    {
        string line;
        bool running = true; // we may want to terminate this loop in some condition.
        while (running)
        {
            line = streamReader.ReadLine();
            if (line != null)
            {
                Console.WriteLine(line);
            }
            else // as per edit, the whole else block can be omitted.
            {
                while (streamReader.EndOfStream)
                {
                    Thread.Sleep(1000); // wait around for n time. This could end up in an infinte loop if the file is not written to anymore. 
                }
            }
       }
    
       // FIXME: The stream reader stops, instead of doing a continous read.
       Console.WriteLine("End of file");
    }
    

    EDIT: You can do without the else block:

    else 
    { 
        while (streamReader.EndOfStream) 
        { 
            Thread.Sleep(1000) 
        } 
    }
    

    Like this:

    while (running)
    {
        line = streamReader.ReadLine();
        if (line != null)
        {
            Console.WriteLine(line);
        }
    }