1

I am generating data every 1 second to write in a file but I want a new file every 5 minutes rather than every 1 second and want to writes all 5 minutes data into that file.

Below code generate a new file every 1 second and for that period data writes in the file. How I can generate a new file every 5 minutes?

 static void Main(string[] args)
    {
        var incremental = 0;

        while (true)
        {
            Go(incremental);

            incremental++;
            Thread.Sleep(1000);
        }
    }


private static void Go(int incremental)
    {
        var fileFullPath = GetDynamicFullPath(@"C:\Temp\");

        using var rewrite = new StreamWriter(fileFullPath);
        rewrite.WriteLine(incremental);
    }

    private static string GetDynamicFullPath(string basePath)
    {
        var dynamicFileName = $"file-{DateTime.Now:yyyyMMddHHmmssfff}.txt";

        return basePath + dynamicFileName;
    } 
Useme Alehosaini
  • 2,998
  • 6
  • 18
  • 26
user584018
  • 10,186
  • 15
  • 74
  • 160

3 Answers3

2

This is exactly the case where the .Net Reactive library (System.Reactive) shines. Install it via NuGet package maneger, then the code will look like this:

1:declare buffer variable:

private readonly Subject<int> _myBuffer = new Subject<int>();

2:setup buffer subscription

_myBuffer.Buffer(TimeSpan.FromMinutes(5))
    .Subscribe(bufferData => Go(bufferData.ToArray()));

3:your Do method is little changed to accept array of ints

private void Go(int[] bufferData)
{
    var fileFullPath = GetDynamicFullPath(@"C:\Temp\");

    using var rewrite = new StreamWriter(fileFullPath);
    rewrite.WriteLine(bufferData);
}

4:finally, the infinite loop

var incremental = 0;

while (true)
{
    _myBuffer.OnNext(incremental++);
    Thread.Sleep(1000);
}

It can not be simpler!

The point is that Buffer method will automatically collect all received data for the specified period. You just need to subscribe to it (start listening to it) and then your `Do' method will be executed every 5 minutes with the buffered data.

Tomas Chabada
  • 2,869
  • 1
  • 17
  • 18
  • At which line exactly it *shines*? I fail to see even a single advantage over Timer + some dedicated collection to hold values. – Sinatr Apr 17 '20 at 07:00
  • @Sinatr, all that magic is crafted in one line of code, the only one: `_myBuffer.Buffer(TimeSpan.FromMinutes(5)).Subscribe(bufferData => Go(bufferData.ToArray()));`. Reactive programming has its advantages, look at http://reactivex.io/, you will see – Tomas Chabada Apr 17 '20 at 07:10
  • I wouldn't comment if you just say "you can use ", but you say "exactly case ... shines". The line you mentioned is equal to creating instance of Timer, which will execute its event handler every 5 minutes. – Sinatr Apr 17 '20 at 07:19
  • @Sinatr, it is not equal. Because it also buffers the input data. Starting the timer does nothing with buffering input data, you must make buffer by yourself – Tomas Chabada Apr 17 '20 at 07:52
  • *"you must make buffer by yourself"* - I can refer to an additional line where you create instance of buffer. That would be creating instance of `Queue` (or similar). So no, there is no additional costs in creating buffer. Means no advantage of using reactive approach. Means nothing "shine". Means not "exactly case". ;) – Sinatr Apr 17 '20 at 08:37
  • @Sinatr Ok then... Try to pust your answer here and I will pick some additional lines from your code (declaring buffer variable, declaring mutex/lock variable, declaring timer/starting timer, locking mechanism for accessing buffer property, cleaning buffer, adding items to buffer...) – Tomas Chabada Apr 17 '20 at 09:00
  • @Sinatr and then persuade others to like `timer` answer more than the `reactive` one – Tomas Chabada Apr 17 '20 at 09:04
  • You can simply use [ConcurrentQueue](https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentqueue-1), see [this answer](https://stackoverflow.com/a/45076799/1997232) as example, `Enque/TryDequeue` will do all the job. – Sinatr Apr 17 '20 at 09:25
  • I know how can that be achieved using `timer` approach. I just found Rx solution to be simpler, more readable and faster to write. Also less error-prone. When using Enqueue/TryDequeue you are also dequeuing items, that were added after the tick (without locking mechanism). As I said before, write complete answer as you wish and we can compare – Tomas Chabada Apr 17 '20 at 09:37
1

I would advice you to use a Timer to flush and create the files all 5 Minutes. You then also should use a SemaphoreSlim to synchronize the access to the StreamWriter, because the callback for the timer may be executed on any Thread-Pool-Thread. So I would do something like:

public static StreamWriter writer;
public static SemaphoreSlim mutex;

static void Main(string[] args)
{
    mutex = new SemaphoreSlim(1,1);

    var incremental = 0;

    using var timer = new Timer(5 * 60 * 1000);

    static void Timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        CreateNewFile();
    }

    timer.Elapsed += Timer_Elapsed;
    timer.AutoReset = true;
    timer.Start();

    while (true)
    {
        WriteData(incremental);
        Thread.Sleep(1000);
        incremental++;
    }
}

private static void WriteData(int data)
{
    mutex.Wait();

    try
    {
        // write you data ...
    }
    finally
    {
        mutex.Release();
    }
}

private static void CreateNewFile()
{
    mutex.Wait();
    try
    {
        if (writer != null)
        {
            writer.Dispose();
            writer = null;
        }

        var fileFullPath = GetDynamicFullPath(@"C:\Temp\");

        writer = new StreamWriter(fileFullPath);
    }
    finally
    {
        mutex.Release();
    }
}

private static string GetDynamicFullPath(string basePath)
{
    var dynamicFileName = $"file-{DateTime.Now:yyyyMMddHHmmssfff}.txt";

    return basePath + dynamicFileName;
}

In addition I would suggest to use the async versions of all these methodes. So WaitAsync(), WriteLineAsync, ...

public static StreamWriter writer;
public static SemaphoreSlim mutex;

static async Task Main(string[] args)
{
    mutex = new SemaphoreSlim(1, 1);

    var incremental = 0;

    using var timer = new Timer(5 * 60 * 1000);

    static async void Timer_ElapsedAsync(object sender, ElapsedEventArgs e)
    {
        await CreateNewFileAsync();
    }

    timer.Elapsed += Timer_ElapsedAsync;
    timer.AutoReset = true;
    timer.Start();

    while (true)
    {
        await WriteDataAsync(incremental);

        await Task.Delay(1000);

        incremental++;
    }
}

private static async Task WriteDataAsync(int data)
{
    await mutex.WaitAsync();

    try
    {
        // write you data ...
        await writer.WriteLineAsync(data.ToString());
    }
    finally
    {
        mutex.Release();
    }
}

private static async Task CreateNewFileAsync()
{
    await mutex.WaitAsync();
    try
    {
        if (writer != null)
        {
            await writer.DisposeAsync();
            writer = null;
        }

        var fileFullPath = GetDynamicFullPath(@"C:\Temp\");

        writer = new StreamWriter(fileFullPath);
    }
    finally
    {
        mutex.Release();
    }
}

private static string GetDynamicFullPath(string basePath)
{
    var dynamicFileName = $"file-{DateTime.Now:yyyyMMddHHmmssfff}.txt";

    return basePath + dynamicFileName;
}
Ackdari
  • 3,222
  • 1
  • 16
  • 33
1

I would suggest the simplest solution:

  • You can store a datetime when the last file was created.
  • You can also define a 5 mins threshold.
  • You should call the file create method when the current datetime is greater than the sum of the last created timestamp and the threshold.

Sample code:

private static readonly TimeSpan threshold = TimeSpan.FromMinutes(5);
private static DateTime lastCreated = DateTime.UtcNow;
public static void Main()
{
    while(true)
    {
        if(DateTime.UtcNow >= lastCreated.Add(threshold))
        {
            //call the file create method
            lastCreated = DateTime.UtcNow;
        }
        Thread.Sleep(1000);
    }
}
Peter Csala
  • 17,736
  • 16
  • 35
  • 75