What is the best method for creating a static class that uses threads?

1.3k Views Asked by At

Let's say I am designing a simple logging class (yes - I know there are those already out there in the wild!) and I want the class to be static so the rest of my code can call it without having to instantiate it first. Maybe something like this:

internal static class Log
{
    private static string _logFile = "";

    internal static void InitializeLogFile(string path)
    {
        ...
    }
    internal static void WriteHeader()
    {
        ...
    }
    internal static void WriteLine(params string[] items)
    {
        ...
    }
}

Now, I want the internals to spin up their own thread and execute in an Asynch manner, possibly using BackgroundWorker to help simplify things. Should I just create a new BackgroundWorker in each method, create a static BackgroundWorker as a private property of the static class, or is there something I am overlooking altogether?

5

There are 5 best solutions below

1
On BEST ANSWER

Good call,

You definitely want the logging operations to occur in a separate thread as the code that is doing the logging. For instance, the accessor methods (such as "logEvent(myEvent)" ) should not block on file I/O operations while the logger logs the event to a file.

Make a queue so that the accessors simply push items onto the queue. This way your code shouldn't block while it is trying to log an event.

Start-up a second thread to empty the internal queue of events. This thread can run on a static private method of your logger class.

The performance drawback comes when you try to ensure thread safety of the underlying event queue. You will need to acquire a lock on the queue every time before a pop or push onto the queue.

Hope this helps.

2
On

You definitely do not want spin up a new thread or BackgroundWorker on each invocation of the methods. I would use the producer-consumer pattern here. As it turns out this is such a common pattern that Microsoft provided us with the BlockingCollection class which simplies the implementation greatly. The nice thing about this approach is that:

  • there is only one extra thread required
  • the Log methods will have asynchronous semantics
  • the temporal ordering of the log messages is preserved

Here is some code to get your started.

internal static class Log
{
  private static BlockingCollection<string> s_Queue = new BlockingCollection<string>();

  static Log()
  {
    var thread = new Thread(Run);
    thread.IsBackground = true;
    thread.Start();
  }

  private static void Run()
  {
    while (true)
    {
      string line = s_Queue.Take();
      // Add code to append the line to the log here.
    }
  }

  internal static void WriteLine(params string[] items)
  {
    foreach (string item in items)
    {
      s_Queue.Add(item);
    }
  }
}
2
On

I think that my recommendation is not exactly what you expect, but I hope it is useful anyway:

  • Don't use a static class. Instead, use a regular class and hold a single instance of it (the singleton pattern); using a dependency injection engine helps a lot with this (I use MS Unity and it works fine). If you define an interface for your logging class as well, your code will be much more testable.
  • As for the threading stuff, if I understand correclty you want the logging work to be performed in separate threads. Are you sure that you really need this? A logger should be light enough so that you can simple call the "Write" methods and expect that your application performance will not suffer.

A last note: you mention the BackgroundWorker class, but if I am not wrong this class is intended for use with desktop applications, not with ASP.NET. In this environment you should probably use something like the ThreadPool class.

Just my 2 euro cents...

0
On

I created a thread safe logging class myself a while back. I used it something like this.

Logging obj = new Logging(filename);
Action<string> log = obj.RequestLog();

RequestLog would return an anonymous method that wrote to its own Queue. Because a Q is thread safe for 1 reader/writer, I didn't need to use any locks when calling log()

The actual Logging object would create a new thread that ran in the background and would periodically check all of the queues. If a Q had a string in it, it would write it to a buffered file stream.

I added a little extra code to the reading thread so for each pass it made on the queues, if there was nothing written, it would sleep an extra 10 ms, up to a max of 100ms. This way the thread didn't spool too much. But if there was heavy writing going on, it would poll the Qs every 10ms.

Here's a snpit of the return code for the requested queue. The "this.blNewData = true" was so I didn't need to hit up every Q to see if any new data was written. No lock involved because a false positive still did no work since all the Qs would be empty anyway.

OutputQueue was the list of Queues that I looped through to see if anything was written. The code to loop through the list was in a lock in case NewQueueLog() was called and caused the list to get resized.

public Action<String> NewQueueLog()
{
    Queue<String> tmpQueue = new Queue<String>(32);
    lock (OutputQueue)
    {
        OutputQueue.Add(tmpQueue);
    }
    return (String Output) =>
    {
        tmpQueue.Enqueue(Output);
        this.blNewData = true;
    };
}

In the end, writing to the log was lock free, which helped when lots of threads were writing.

4
On

You only want to have 1 thread per log file/db. Otherwise, the order of items in the log is unreliable. Have a background thread that pulls from a thread-safe queue and does the writing.