1

I have a Windows service application that has a number of different jobs that need to be done in parallel and are spawned on three different schedules. However I want to make sure that at no point of time more than N tasks are running.

This is not a duplicate of this question, because that is about limiting the number of tasks that are started per second, not the number that are running concurrently. Also not a duplicate of this because my tasks are on separate schedules so cannot be queued simultaneously.

Here is some code to provide context:

namespace WindowsService1
{
    public partial class Service1 : ServiceBase
    {
        List<Task> ServiceTasks;
        private List<Timer> ServiceTimers;
        private static int N = 10;

        public Service1()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            List<double> Times = new List<double>(){1000d,2000d,5000d};
            for(int i = 0; i<3 ; i++)
            {
                 var t = new Timer(Times[i]);
                 t.AutoReset = true;
                 ServiceTimers.Add(t);
            }
            ServiceTimers[0].Elapsed += Timer1Elapsed_DoSomething;
            ServiceTimers[0].Start();
            ServiceTimers[1].Elapsed += Timer2Elapsed_DoSomething;
            ServiceTimers[1].Start();
            ServiceTimers[2].Elapsed += Timer3Elapsed_DoSomething;
            ServiceTimers[2].Start();
        }

         private void Timer1Elapsed_DoSomething(object sender, ElapsedEventArgs e)
         {
            ServiceTasks.Add(Task.Factory.StartNew(() => ServiceWork.DoTask1()));
         }

         private void Timer2Elapsed_DoSomething(object sender, ElapsedEventArgs e)
         {
            ServiceTasks.Add(Task.Factory.StartNew(() => ServiceWork.DoTask2()));
         }

         private void Timer3Elapsed_DoSomething(object sender, ElapsedEventArgs e)
         {
            ServiceTasks.Add(Task.Factory.StartNew(() => ServiceWork.DoTask3()));
         }  
    }
}

How can I modify this code to ensure that only N tasks will be running at any given time?

Community
  • 1
  • 1
ConfusedMan
  • 562
  • 1
  • 4
  • 12
  • possible duplicate of [Limit the number of Tasks in Task.Factory.Start by second](http://stackoverflow.com/questions/18771524/limit-the-number-of-tasks-in-task-factory-start-by-second) –  Jul 31 '15 at 02:13
  • How is this different from [System.Threading.Tasks - Limit the number of concurrent Tasks](http://stackoverflow.com/questions/2898609/system-threading-tasks-limit-the-number-of-concurrent-tasks) ? (If you're concerned that the fact that the application is a service might make a difference, it doesn't.) – Harry Johnston Jul 31 '15 at 03:38
  • @HarryJohnston, thank you. But, in the question you pointed out, all 100 tasks are spawned simultaneously. In my case, the tasks are starting at different times in different functions, I am not sure how to process incoming tasks as new tasks come in. I would be much obliged if you can explain how to do that, or explain how the answer is the same as in the question you pointed out, if that is the case. – ConfusedMan Jul 31 '15 at 16:36
  • 1
    OK, I think that's a legitimate difference, so probably not a duplicate. [One of the answers to that question](http://stackoverflow.com/a/4088240/886887) does cover this situation, but there isn't very much detail, it should probably be expanded on. It isn't entirely clear to me how the linked code works. – Harry Johnston Jul 31 '15 at 21:41

1 Answers1

1

SemaphoreSlim is a class that allows you to do just that. It's a lock for N simultaneous "threads".

Like that:

SemaphoreSlim s = ...;
async Task F() {
 await s.WaitAsync();
 DoWork();
 s.Release();
}

You can use the synchronous API as well if you don't mind 1MB of stack usage per thread waiting.


This:

 Task.Factory.StartNew(() => ServiceWork.DoTask3())

becomes:

Task.Run(async () => {
 await s.WaitAsync();
 try {
  ServiceWork.DoTask3();
 } finally {
  s.Release();
 }
});

And you can start an unlimited number of these and they will self-throttle.

The list of tasks is still required so that you can shut down the service cleanly by waiting for all tasks to complete and handling their errors.

usr
  • 168,620
  • 35
  • 240
  • 369