Search code examples
c#.netasynchronoustask-parallel-librarysolid-principles

Threading and SOLID principle


I have a the following code setup

public interface ILogger
{
  void WriteData(string Data);
}

public class Logger : ILogger
{
  public void WriteData(string Data)
  {
     //Write to disk   
  }
}

public interface ILogic
{
  void ProcessData(string Data);
}

public class Logic : ILogic
{
  private ILogger Logger;

  public Logic(ILogger Logger)
  {
    this.Logger = Logger;
  }

  public void ProcessData(string Data)
  {
     //Do stuff
     Logger.WriteData("Data to write");
  }
}

public class MainEntryPointClass
{
     private BlockingCollection<string> DataInCollection;
     private Task DataInTask;
     private CancellationTokenSource CancellationTokenSource;

     public Start()
     {
        InitializeDataIn();
     }

        private void InitializeDataIn()
        {
            CancellationTokenSource = new CancellationTokenSource();
            DataInCollection = new BlockingCollection<DataInContents>();
            DataInTask = Task.Factory.StartNew(() => ProcessDataIn(CancellationTokenSource.Token));
        }

      private void ProcessDataIn(CancellationToken CancelToken)
        {
            while (!CancelToken.IsCancellationRequested)
            {
                foreach (var item in DataInCollection.GetConsumingEnumerable())
                {
                    Logic.ProcessData(item);
                }
            }

        }
}

So I create a new Task in my main class and then data is added to the DataInCollection to queue up data as it comes in, we're talking every 30ms or so. This gets processed successfully.

I now want to write data to file on a separate thread just so if there is a disk problem the main logic checking isn't impacted. If there is a disk problem then the logic can continue. I'm just not sure where I do the file writing on the separate thread? Is it in the Main class, the Logic class or the Logger class?


Solution

  • It's Logger's responsibility to ensure that it doesn't block the caller. It can use many different strategies for that. You don't want to bake those strategies into the class that uses it.

    I'd enqueue the message into a BlockingCollection<T>, and have a single IO thread write it out to the disk.

    I also recommend imitating an existing logging interface, such as Common.Logging's ILog so you can easily switch to an existing logging framework if your "no third party" requirement gets ever lifted.

    Something like:

    class AsyncLogger:ILogger
    {
      public AsyncLogger(ILogger backingLogger)
      {
        new Thread(()=>
          {
            while(true)
            {
              var data=_queue.Take();
              _backingLogger.WriteData(data);
            }
          }
        ).Start();
      }
    
      public void WriteData(string data)
      {
         _queue.Enqueue(data);
      }
    }
    

    (I omitted stuff like a termination condition for IO thread, fields, handling of multiple loggers,...)