4

Hi I am new to multithreading and would like to ask for your advice and guidance.

We have a service running on our server to poll data for notifications on our clients. We wanted the service to process data faster. Currently, our existing service polls and processes data on a single thread which sometimes causes delay to the notifications on hourly bases. My plan is to use ThreadPool to process data concurrently. I have this piece of code that simulates my plan and idea.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Security;
using System.Text;
using System.Threading;
using System.Xml;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Web; 

namespace ThreadPooling
{
    class Program
    {
        static int nMaxRecord = 0;
        static ManualResetEvent mre = new ManualResetEvent(false);
        static Timer TestThread = null;
        static void Main(string[] args)
        {
            TestThread = new Timer(new TimerCallback(ProcessWithThreadPoolMethod), null, 500, Timeout.Infinite);
            Thread.Sleep(Timeout.Infinite);
        }

        static void ProcessWithThreadPoolMethod(object ostate) // Sample processing of data
        {
            nMaxRecord = 1300;
            ThreadPool.SetMaxThreads(3, 0);
            for (int i = 0; i < 1300; i++)
            {
                ThreadPool.QueueUserWorkItem(ProcessWithThreadMethod, i);
            }

            mre.WaitOne();
            Console.WriteLine("Test");

            TestThread.Change(5000, Timeout.Infinite);
        }

        static void ProcessWithThreadMethod(object callback)
        {
            for (int i = 0; i <= 10; i++)
            {
                Console.WriteLine((int)callback);
            }

            if(Interlocked.Decrement(ref nMaxRecord) == 0)
            {
                mre.Set();
            }
        }
    }
}

While running the console application, I noticed that the thread count keeps increasing although I limited the maxthreads in the ThreadPool by 3. Am I doing the right thing? Would like to ask some guidance and Pros and Cons on my concept.

  • Maybe read the "Remarks" section and check the points in there: https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool.setmaxthreads?redirectedfrom=MSDN&view=netframework-4.7.2#System_Threading_ThreadPool_SetMaxThreads_System_Int32_System_Int32_ , also check the return value of SetMaxThreads. Is it `false`? – Fildor Sep 13 '18 at 07:05
  • You shall always use `TPL` instead of `ThreadPool` / `Thread` – Mrinal Kamboj Sep 13 '18 at 07:07
  • @MrinalKamboj Can you give reasons for that claim? Don't get me wrong - I agree it should be benefitial, but "always" rings my bells. – Fildor Sep 13 '18 at 07:08
  • 1
    @MrinalKamboj; is that available in .net 3.5? – Stefan Sep 13 '18 at 07:09
  • Not a claim anymore, TPL has replaced all aspects that need to be ever done using Thread API and its strongly discouraged to use the Threading API directly in the innumerable blogs and technical documentation by various experts – Mrinal Kamboj Sep 13 '18 at 07:10
  • 1
    @MrinalKamboj _"Starting with the **.NET Framework 4**, the TPL is the preferred way"_ [Source](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl) OP is on 3.5 – Fildor Sep 13 '18 at 07:11
  • 1
    @Stefan missed out that part, yes its from .Net 4.0 onward – Mrinal Kamboj Sep 13 '18 at 07:12
  • @Stefan Can I use Semaphore and Threadpool at the same time? – Jonathan Daniel Sep 13 '18 at 09:12
  • 1
    @JonathanDaniel: yes, the ThreadPool just regulates the amount of threads being spawned. The semaphore is like a bouncer: it regulate the concurrent active thread. Note: it does this by waiting, so if you fire 1300 threads, and permit 3 to be active at the same time; 1297 will be waiting. – Stefan Sep 13 '18 at 09:51

2 Answers2

2

You should test the return value of:

ThreadPool.SetMaxThreads(3, 0); //returns false on normal machines.

It's unable to process the change due to this:

You cannot set the maximum number of worker threads or I/O completion threads to a number smaller than the number of processors on the computer. To determine how many processors are present, retrieve the value of the Environment.ProcessorCount property. In addition, you cannot set the maximum number of worker threads or I/O completion threads to a number smaller than the corresponding minimum number of worker threads or I/O completion threads. To determine the minimum thread pool size, call the GetMinThreads method.

See: MSDN


So, what you can do is something like this;

ThreadPool.SetMaxThreads(16, 16);

But I assume you were trying to down-throttle the ThreadPool. In general that's not a good idea. You'll need an alternative for such logic.

A semaphore might be an option, as described here, or the pattern @Fildor describes.

Stefan
  • 17,448
  • 11
  • 60
  • 79
  • Thank you!!!, my bad I was not able to read the SetMaxThread documentation. So we can't limit the maximum threads lower from the number of processors. We wanted to avoid CPU Spikes that's why I set it by 3. When I tested the service on our staging server I also noticed it created 1K + of Threads although the CPU usage is around 40% - 50% is that normal? – Jonathan Daniel Sep 13 '18 at 07:16
  • 2
    Then use Producer-Consumer-Pattern. Don't use one thread per item, but keep items in a queue and consume them with only how many threads you see fit. @JonathanDaniel – Fildor Sep 13 '18 at 07:17
  • ` I also noticed it created 1K + of Threads although the CPU usage is around 40% - 50% is that normal` that depends on what's it doing. 1k+ threads is a lot. Also the CPU usage is high. But if your system is processing a lot, than it makes sense. If you are not expecting these loads: something is wrong. – Stefan Sep 13 '18 at 07:22
  • @Fildor Thanks for the advice. Do you have any article or examples on how to get started with Producer-Consumer Pattern? If you won't mind. – Jonathan Daniel Sep 13 '18 at 07:23
  • @JonathanDaniel Last time I read up on it, .net 2.0 was current ... so my sources are really outdated. I suggest you google "C# Producer-Consumer Pattern" and see what you get. You'll probably have to filter for .net 3.5 . BTW: Upgrading to >= 4 is no option? – Fildor Sep 13 '18 at 07:27
  • @Fildor thanks, we can't upgrade to 4 apparently, since most of our services and project builds are using 3.5 and are tightly coupled with other projects as well. – Jonathan Daniel Sep 13 '18 at 07:29
  • 1
    Just found this: https://stackoverflow.com/a/47179576/982149 . Maybe it can get you "inspired". – Fildor Sep 13 '18 at 07:33
0

You cannot throttle the threadpool, but why not just have a simple increment/decrement counter that you check before launching a new thread ?

In pseudo code-

volatile int currentThreadCount = 0;

void myWorkLauncher()
{
   while(<I have work to do>)
   {
      if(currentThreadCount < threshold)
      {
          currentThreadCount ++;
          ThreadPool.QueueUserWorkItem(workerFunc);
      }
      else
      {
          Thread.Sleep(500);
      }
   }
   Thread.Sleep(500);
}

The last line of the workedFunc just decrements the value.

You can do all sorts of fancy stuff like wrapping your workerFunc in an Action() which itself decrements the counter, preventing your workerFunc from needing any connection to the myWorkLauncher class. Alternatively you can replace the simplistic Thread.Sleep with a AutoResetEvent or similar.

PhillipH
  • 6,182
  • 1
  • 15
  • 25