1

I am running a Thread which get called in a static interval. In this Thread I am running several Tasks (20-200). All this works fine, but when the Thread gets called the first time, it takes like ~1 sec for one Tasks to start. As soon as the while loop is in the second loop or when the Thread stops, and gets called a second time, the problem is gone.

public static async void UpdateThread()
{          
     while(!stop)
     {       
         foreach (DSDevice device in DSDevices)
         {
            var task = Task.Run(() =>
            {
                // Delay is measured here
                // Do Stuff
            });
        }
        //No Delay
        await Task.WhenAll(tasks);
        Thread.Sleep(Sleeptime);
    }
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Toby_Stoe
  • 216
  • 1
  • 11
  • 2
    Then `// Do Stuff` is slow. Benchmark it. If you don't show or explain what it does, we can't give any meaningful insights. Is it JIT, I/O, ... – CodeCaster Sep 29 '22 at 06:01
  • Tasks (except I/O-bound varieties) utilise a thread from the thread pool. The pool grows as required. For all we know you are running 50 threads before the above code runs causing the pool to grow, something that may take a bit of time. –  Sep 29 '22 at 06:04
  • Yes, //Do Stuff is slow. But it shouldnt really matter. because i'm measuring before it. If i understand it correctly, all of these Tasks should run in parallel, so there is no reason a task is waiting to start, until another is complete. And even if so, why is it only the first time? – Toby_Stoe Sep 29 '22 at 06:05
  • 1
    C# is a JIT compiled language. Plus lots of framework code is initialised on first use. – Jeremy Lakeman Sep 29 '22 at 06:05
  • "Delay is measured here" - how do you do this? – Klaus Gütter Sep 29 '22 at 06:21
  • Did you measure it in Release build, or debug? – Rand Random Sep 29 '22 at 06:54
  • 2
    Why are you combining Tasks with Threads? Its better to use each one for their own usage (mostly Tasks as they are the "better" version). – Roy Avidan Sep 29 '22 at 07:08
  • @KlausGütter: Actual, I'm just writing the progress into the Console. If needed i can measure the exact value. – Toby_Stoe Sep 29 '22 at 08:04
  • @RandRandom Release Build – Toby_Stoe Sep 29 '22 at 08:05
  • 1
    [Here](https://stackoverflow.com/questions/73328524/are-there-more-tasks-being-performed-than-threads-whats-happening/73328718#73328718 "Are there more tasks being performed than threads? What's happening?") is a possible answer to your question. – Theodor Zoulias Sep 29 '22 at 08:11
  • 1
    The reason that the threadpool imposes a delay before creating each new thread once a certain limit is exceeded is to avoid the system grinding to a halt as hundreds of threads are created simultaneously by an errant process. – Matthew Watson Sep 29 '22 at 08:29

1 Answers1

3

The Task.Run runs the code on the ThreadPool, and the ThreadPool creates initially a limited number of threads on demand. You can increase this limit with the SetMinThreads method:

ThreadPool.SetMinThreads(200, 200);

...but check out the documentation before doing so. Increasing this threshold is not something that you should do without thinking. Having too many ThreadPool threads defeats the purpose of having a pool in the first place. Think whether it's better to have a dedicated thread per device, for the whole life-time of the program.

As a side note, if I was in your shoes I would not parallelize the processing of the devices by creating tasks manually. I would use the Parallel.ForEach method, which exists for exactly this kind of job. As a bonus it allows to control the degree of parallelism, either to a specific number or to -1 for unlimited parallelism:

public static async Task MonitorDevicesPeriodicAsync(
    CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        Task delayTask = Task.Delay(MonitorDevicesPeriodMilliseconds);
        await Task.Run(() =>
        {
            ParallelOptions options = new() { MaxDegreeOfParallelism = -1 };
            Parallel.ForEach(DSDevices, options, device =>
            {
                // Do Stuff with device
            });
        });
        await delayTask;
    }
}

The Parallel.ForEach invokes also the delegate on the ThreadPool (by default), and it can saturate it as easily as the await Task.WhenAll(tasks) approach, so you might need to use the ThreadPool.SetMinThreads method as well.

Three more off topic suggestions: prefer async Task over async void. Async void is intended for event handler only. Also use a CancellationToken for stopping the while loop instead of a non-volatile bool stop field. In a multithreaded environment, it's not guaranteed that the mutation of the field from one thread will be visible from other threads. Alternatively declare the field as volatile. Finally use the Task.Delay instead of the Thread.Sleep, create the Task.Delay task at the start of the iteration and await it at the end, for a stable periodic invocation.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Out of curiosity, how did change your mind from a "possible" answer to a definite answer? Did you benchmark it and verify that this answer is the reason of the delay 99.9%? – Rand Random Sep 29 '22 at 08:54
  • 1
    @RandRandom I don't write 99.9% answers. My answers are closer to 80%. If the answer is wrong, I can edit it later or delete it. – Theodor Zoulias Sep 29 '22 at 08:57
  • I typically only give 10%. :) | sorry, if the percentage came of as rude/strange - was just curious since in comment on the question you said "possible“ and wanted to know if you found out that it isn’t only "possible" but in fact the real culprit – Rand Random Sep 29 '22 at 09:01
  • 1
    @RandRandom this answer is just intuition. There was no testing involved. I have seen people having similar problems many times. – Theodor Zoulias Sep 29 '22 at 09:06