Search code examples
c#loggingtimerintervals

Logging data from structs in interval 10-15 ms in C#


I have to prepare Logger class which will be saving data from 3 structs in interval of 10-15 ms. My approach to this problem is below:

    public class Logger
{
    // Variables
    private Task loggerTask;
    public bool IsActive { get; set; }

    // Constructor
    public Logger()
    {

    }

    private void Logging()
    {
#if DEBUG
            System.Diagnostics.Debug.WriteLine("Logging has been started.");
#endif

        FileStream fs = new FileStream($"./log {DateTime.Now.ToString("dd.MM HH.mm.ss")}.txt", FileMode.Create, FileAccess.Write);
        StreamWriter sw = new StreamWriter(fs, Encoding.Default);

        try
        {
            Queue<double> times = new Queue<double>();
            Queue<Attitude> attitudes = new Queue<Attitude>();
            Queue<LocalPositionNed> positions = new Queue<LocalPositionNed>();
            Queue<SetPositionTargetLocalNed> setpoints = new Queue<SetPositionTargetLocalNed>();

            // Logs data
            DateTime start = DateTime.Now;
            DateTime last = start;
            DateTime now;

            while (IsActive)
            {
                now = DateTime.Now;

                if ((now - last).TotalMilliseconds < 16)
                    continue;

                last = now;
                
                times.Enqueue((now - start).TotalMilliseconds);
                attitudes.Enqueue(GCS.Instance.Drone.Attitude);
                positions.Enqueue(GCS.Instance.Drone.LocalPositionNed);
                setpoints.Enqueue(GCS.Instance.Offboard.SetPoint);
            }

            // Save data
            for(int i = 0; i < times.Count; i++)
            {
                sw.WriteLine($"{times.ElementAt(i)}\t" +
                    $"{attitudes.ElementAt(i).Pitch}\t" +
                    $"{attitudes.ElementAt(i).Roll}\t" +
                    $"{attitudes.ElementAt(i).Yaw}\t" +
                    $"{attitudes.ElementAt(i).Pitchspeed}\t" +
                    $"{attitudes.ElementAt(i).Rollspeed}\t" +
                    $"{attitudes.ElementAt(i).Yawspeed}\t" +
                    $"{positions.ElementAt(i).X}\t" +
                    $"{positions.ElementAt(i).Y}\t" +
                    $"{positions.ElementAt(i).Z}\t" +
                    $"{positions.ElementAt(i).Vx}\t" +
                    $"{positions.ElementAt(i).Vy}\t" +
                    $"{positions.ElementAt(i).Vz}\t" +
                    $"{setpoints.ElementAt(i).Vx}\t" +
                    $"{setpoints.ElementAt(i).Vy}\t" +
                    $"{setpoints.ElementAt(i).Vz}\t");
            }

        }
        catch (Exception ex)
        {
#if DEBUG
                System.Diagnostics.Debug.WriteLine($"Logging exception: {ex.Message}");
#endif
        }
        finally
        {
            sw.Dispose();
            fs.Dispose();
        }

#if DEBUG
            System.Diagnostics.Debug.WriteLine("Logging has been ended.");
#endif
    }

    // Static method
    public void Start()
    {
        IsActive = true;

        loggerTask = new Task(Logging);
        loggerTask.Start();
    }
    public void Stop()
    {
        IsActive = false;
    }
}

I have problem with intervals, because they are varying about 5-8 ms. My project requires maximum varying of 1-2 ms. Does anyone have idea how I can improve my approach.

Thank you for your responses.


Solution

  • The biggest issue is probably using DateTime.Now, this has poor resolution and is not appropriate for this kind of task.

    A simple alternative that would be more appropriate is a stopwatch.

            var delay = 16;
            var stopwatch = Stopwatch.StartNew();
            long current = 0;
            long previous;
            var next = stopwatch.ElapsedMilliseconds + delay;
            for (int i = 0; i < 100; i++)
            {
    
                while (next > stopwatch.ElapsedMilliseconds)
                {
                    // Spin
                }
    
                next = stopwatch.ElapsedMilliseconds + delay;
    
                previous = current;
                current = stopwatch.ElapsedMilliseconds;
                var delta = current - previous;
                Console.WriteLine("Delta: " + delta);
            }
    

    As mentioned in the comments, this does not provide any strong guarantees, merely a best effort. There are also better ways to do this that does not waste an entire thread doing nothing, for example using multi media timers. But it might be adequate for testing/debugging purposes as long as the load on the CPU is light.