C# Design pattern for periodic execution of multiple Threads

766 Views Asked by At

I have a below requirement in my C# Windows Service.

  1. At the starting of Service, it fetches a collection of data from db and keeps it in memory.
  2. Have a business logic to be executed periodically from 3 different threads.
  3. Each thread will execute same bussiness logic with different subset of data from the data collection mentioned in step 1. Each thread will produce different result sets.
  4. All 3 threads will run periodically if any change happened to the data collection.

When any client makes call to the service, service should be able to return the status of the thread execution.

I know C# has different mechanisms to implement periodic thread execution. Timers, Threads with Sleep, Event eventwaithandle ect., I am trying to understand Which threading mechanism or design pattern will be best fit for this requirement?

2

There are 2 best solutions below

0
On

A more modern approach would be using tasks but have a look at the principles

namespace Test {

public class Program {

    public static void Main() {

        System.Threading.Thread main = new System.Threading.Thread(() => new Processor().Startup());
        main.IsBackground = false;
        main.Start();
        System.Console.ReadKey();
    }
}

public class ProcessResult { /* add your result state */ }

public class ProcessState {

    public ProcessResult ProcessResult1 { get; set; }
    public ProcessResult ProcessResult2 { get; set; }
    public ProcessResult ProcessResult3 { get; set; }
    public string State { get; set; }
}

public class Processor {

    private readonly object _Lock = new object();
    private readonly DataFetcher _DataFetcher;
    private ProcessState _ProcessState;

    public Processor() {
        _DataFetcher = new DataFetcher();
        _ProcessState = null;
    }

    public void Startup() {
        _DataFetcher.DataChanged += DataFetcher_DataChanged;
    }

    private void DataFetcher_DataChanged(object sender, DataEventArgs args) => StartProcessingThreads(args.Data);

    private void StartProcessingThreads(string data) {

        lock (_Lock) {
            _ProcessState = new ProcessState() { State = "Starting", ProcessResult1 = null, ProcessResult2 = null, ProcessResult3 = null };

            System.Threading.Thread one = new System.Threading.Thread(() => DoProcess1(data)); // manipulate the data toa subset 
            one.IsBackground = true;
            one.Start();

            System.Threading.Thread two = new System.Threading.Thread(() => DoProcess2(data)); // manipulate the data toa subset 
            two.IsBackground = true;
            two.Start();

            System.Threading.Thread three = new System.Threading.Thread(() => DoProcess3(data)); // manipulate the data toa subset 
            three.IsBackground = true;
            three.Start();
        }
    }

    public ProcessState GetState() => _ProcessState;

    private void DoProcess1(string dataSubset) {
        // do work 
        ProcessResult result = new ProcessResult(); // this object contains the result
        // on completion
        lock (_Lock) {
            _ProcessState = new ProcessState() { State = (_ProcessState.State ?? string.Empty) + ", 1 done", ProcessResult1 = result, ProcessResult2 = _ProcessState?.ProcessResult2, ProcessResult3 = _ProcessState?.ProcessResult3 };
        }
    }

    private void DoProcess2(string dataSubset) {
        // do work 
        ProcessResult result = new ProcessResult(); // this object contains the result
        // on completion
        lock (_Lock) {
            _ProcessState = new ProcessState() { State = (_ProcessState.State ?? string.Empty) + ", 2 done", ProcessResult1 = _ProcessState?.ProcessResult1 , ProcessResult2 = result, ProcessResult3 = _ProcessState?.ProcessResult3 };
        }
    }

    private void DoProcess3(string dataSubset) {
        // do work 
        ProcessResult result = new ProcessResult(); // this object contains the result
        // on completion
        lock (_Lock) {
            _ProcessState = new ProcessState() { State = (_ProcessState.State ?? string.Empty) + ", 3 done", ProcessResult1 = _ProcessState?.ProcessResult1, ProcessResult2 = _ProcessState?.ProcessResult2, ProcessResult3 = result };
        }
    }
}

public class DataEventArgs : System.EventArgs {

    // data here is string, but could be anything -- just think of thread safety when accessing from the 3 processors
    private readonly string _Data;

    public DataEventArgs(string data) {
        _Data = data;
    }

    public string Data => _Data;
}

public class DataFetcher {
    //  watch for data changes and fire when data has changed
    public event System.EventHandler<DataEventArgs> DataChanged;
}

}

0
On

The simplest solution would be to define the scheduled logic in Task Method() style, and execute them using Task.Run(), while in the main thread just wait for the execution to finish using Task.WaitAny(). If a task is finished, you could Call Task.WaitAny again, but instead of the finished task, you'd pass Task.Delay(timeUntilNextSchedule). This way the tasks are not blocking the main thread, and you can avoid spinning the CPU just to wait. In general, you can avoid managing directly in modern .NET

Depending on other requirements, like standardized error handling, monitoring capability, management of these scheduled task, you could also rely on a more robust solution, like HangFire.