Search code examples
c#multithreadingasync-awaitwindows-servicestask-parallel-library

c# multiple tasks running in background independently without blocking each other


I have a C# Windows Service that runs a few tasks inside.

One of the tasks is a infinite async looping and the others are triggered from a Timer and then execute the task.

     private readonly QueueProcessor _queueProcessor;

     protected override void OnStart(string[] args)
            {
               // first task
                _queueTask = _queueProcessor.Run(_cancellation.Token);
    
                // second task
                affiliate_timer = new System.Timers.Timer();
                affiliate_timer.AutoReset = true;
                affiliate_timer.Interval = _model.Interval_Affiliate * 60000;
                affiliate_timer.Elapsed += new 
                System.Timers.ElapsedEventHandler(affiliate_timer_Elapsed);

                // third task
                invoice_timer = new System.Timers.Timer();
                invoice_timer.AutoReset = true;
                invoice_timer.Interval = _model.Interval_Invoice * 60000;
                invoice_timer.Elapsed += new 
                System.Timers.ElapsedEventHandler(invoice_timer_Elapsed);
            }
    

private void invoice_timer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
            {
               
                if (!_isAffiliateBusy)
                {
                    _isAffiliateBusy= true;
    
                    var task = Task.Run(() => StartAffiliateTask());
                    task.Wait();
    
                    _isAffiliateBusy= false;
                }
                 
            }

     
     private void invoice_timer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
            {
               
                if (!_isInvoiceBusy)
                {
                    _isInvoiceBusy = true;
    
                    var task = Task.Run(() => StartInvoiceTask());
                    task.Wait();
    
                    _isInvoiceBusy = false;
                }
                 
            }
    

  private void StartAffiliateTask()
            {
                _affiliateModule = new Modules.Affiliate();
                _affiliateModule.RunSync();
            }

     private void StartInvoiceTask()
            {
                _invoiceModule = new Modules.Invoice();
                _invoiceModule.RunSync();
            }

This is my QueueProcessor class that implements await/async to execute a infinite looping job:

  public class QueueProcessor
    {
        private readonly IQueueBroker _serviceBroker;

        public QueueProcessor()
        {
        }

        public async Task Run(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                var receiveMessageResponse = await _serviceBroker.ReceiveMessageAsync("test", cancellationToken);
                if (!receiveMessageResponse.Messages.Any())
                {
                    continue;
                }

                foreach (var message in receiveMessageResponse.Messages)
                {
                    
            // some other tasks here...

                    await _serviceBroker.DeleteMessageAsync(message, cancellationToken);
                }
            }
        }
    }

My Affiliate and Invoice module classes doesn't implement any await/async code inside looks like this:

public class Affiliate
    {
        /// <summary>
        /// Start the sync process
        /// </summary>
        public void RunSync()
        {  
            try
            {
                // some code here...
            }
            catch (Exception ex)
            {
                
            }
           
        }
 }

My question is:

When my queue procesor infinite loop is running, does my other tasks that are triggered by the timers still can run independently?

When I use:

 var task = Task.Run(() => StartAffiliateTask());
                    task.Wait();

Does the Wait method stop the whole service thread until this task is finished? or that won't block my StartInvoiceTask to run independantly?

Any recommendation on the best way to have my 3 tasks running independant on each other?


Solution

  • Summing up multiple potential issues:

    1. Race condition (access/write to _isBusy).
    2. Potential deadlock (in low ThreadPool size).
    3. Potential incosistent state of flag in case of errors or thread aborts (_isBusy can be left in 'true' state).

    Further I will assume your 'task' should be running in single instance, so we will disgard timer callbacks if it is still running. You should change your timer event handlers like so (best to just wrap it in some kind of class):

            //the flag, do mention volatile modifier - it tells particular 
            //systems to watch for variable changes by reference, 
            //instead of just copying it into thread stack by value.
            private volatile bool _isAffiliateBusy = false;
    
            //sync object for flag to eliminate race condition
            private object _affiliateSync = new object(); 
            private void affiliate_timer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
            {
                //very fast lookup at flag to filter threads which comes when task is still processing
                if(_isAffiliateBusy)
                    return;
                lock(_affiliateSync) //taking lock
                {
                    //checking again for those threads which 'happen' to be faster than you think.
                    if(_isAffiliateBusy)
                        return;
                    //aquire lock for business 'task'
                    _isAffiliateBusy = true;
                }
                try
                {
                    StartAffiliateTask();
                }
                finally
                {
                    //resetting singleton business 'task' lock.
                    //do not forget to use finally block, to handle disposing
                    //even if something rise up in 'try' section - you will not be left with invalid state of flag.
                    _isAffiliateBusy = false; 
                }
            }