-1

I have an application which will spin up a List of threads then kick them off one after another until a predefined thread limit has been hit, once this happens it will wait until a thread has finished before starting another. I want to implement a way to exit all threads that are currently running if a threshold of consecutive errors is reached. I have got as far as clearing the list of threads to process, so no more will be started but I'm not sure how to go about closing the remaining threads that are running once the thread limit has been reached, I recognise one thread can't talk to another and that's where Im getting hung up..I've provided a cut down version of main method below.

public static void Run(string distinguishedName, string hrExtractFile, string sanctionsFileLocation, string exemptionsFileLocation, string url, string soapAction, int threadLimit)
        {

            UsersList Employees = new UsersList(distinguishedName, hrExtractFile); //imports users from HR file
            int errorcount = 0;

            ManualResetEvent resetEventThreadComplete = new ManualResetEvent(false);
            ManualResetEvent resetEventNoMoreThreads = new ManualResetEvent(false);
            List<Thread> threads = new List<Thread>();
            int toProcess = Employees.Count;

            for (int i = 0; i < Employees.Count; i++)
            {
                int current = i;
                threads.Add(new Thread(delegate ()
                {
                    User u = Employees[current];
                    User drUser = new User();

                    try
                    {
                        drUser = fetchUser(u, url, soapAction);
                        bool userExists = false;

                        if (drUser != null)
                        {
                            userExists = true;
                        }


                        //removes a user if they're in the deleted users OU as well as being in system
                        if (u.IsDeleted)
                        {
                            if (userExists == true)
                            {
                                Console.WriteLine("Removing " + u.AccountName);
                                Log.writeToLogs("activitylog.txt", "Removing " + u.AccountName + ":", u, drUser);
                                DeleteUser(u, url, soapAction);

                            }

                        }
                            errorcount = 0;
                        }


                    }

                    catch (Exception e)
                    {
                        if (errorcount <= 5)
                        {
                            Log.writeToLogs("syslog.txt", e.ToString());
                            Log.writeToLogs("activitylog.txt", u.AccountName + " - Failed to true up!");
                            Console.WriteLine("Failed on " + u.AccountName + ": An error occured, check logs for details");
                            errorcount++;
                        }
                        else
                        {
                            lock (_syncObject)
                            {
                                //removes threads from list of threads that are pending
                                if (threads.Count > 0)
                                {
                                    threads.Clear();
                                }
                            }
                        }

                    }

                    resetEventThreadComplete.Set();

                    if (Interlocked.Decrement(ref toProcess) == 0)
                        resetEventNoMoreThreads.Set();

                }));
            }


            /*
             * Kicks off the first x number of threads (where x = threadLimit) and removes them from list of pending threads  
             */
            for (int i = 0; i < threadLimit; i++)
            {
                if (threads.Count < 1)
                    break;
                //runningThreads++;
                threads[0].Start();
                threads.RemoveAt(0);
            }

            /*
             *Controls the initiation of thread exection; When one thread finishes, another will be started and removed from the list of pending threads  
             */
            while (threads.Count > 0)
            {
                resetEventThreadComplete.WaitOne();
                resetEventThreadComplete.Reset();
                threads[0].Start();
                threads.RemoveAt(0);
            }

            if (toProcess > 0) resetEventNoMoreThreads.WaitOne();

            //Log.sendLogs();

        }
Muz
  • 39
  • 6
  • Why this is a asp.net tagged? – VMAtm Feb 22 '17 at 00:44
  • You say,"I recognise one thread can't talk to another", but that's exactly what you need to do so that the threads can know when to naturally exit. – Enigmativity Feb 22 '17 at 02:22
  • Sorry, limited experience with multithreading. I think what I meaning was one child thread cannot directly access another child thread, they would need to change something outside of their scope to indicate to the other what needs to happen -Is that correct? – Muz Feb 22 '17 at 02:38
  • There are many questions with answers already on Stack Overflow that discuss correct and safe signaling to threads to cancel or otherwise have them terminate early, including the marked duplicate. Note that the modern idiom for this involves using `Task.Run()` to create the background worker, and `CancellationTokenSource` to provide the mechanism for cancelling. If you do it right, you can even have your `Task` object automatically set to the cancelled state if it terminates early (helpful for a variety of reasons, such as resolving race conditions after the fact). – Peter Duniho Feb 22 '17 at 03:55

1 Answers1

-2

Assuming you don't mind killing your threads (not usually a great idea), you could use some code like this, just before clearing the list:

if (threads.Count > 0)
{
    threads.ForEach(t => t.Abort());  //Kill all threads
    threads.Clear();
}

That being said, you should probably not use Thread.Abort(), if you believe this poster:

Did you read the "remark" section on the MSDN? NEVER USE ABORT. IT IS INTENDED AS LAST RESORT. IF YOU USE THREAD.ABORT, SKY MAY FALL DOWN, AND KITTEN WILL BE KILLED

Whatever method you choose, you can put it into that ForEach expression, e.g. if you prefer to use Interrupt:

threads.ForEach(t => t.Interrupt());  

Another (far simpler) approach is to modify your threads to check a global flag variable and exit cleanly when it is set. In your main program, when you wish to kill the threads, just set the flag. Be sure to use a volatile variable, or better yet, a WaitHandle.

Community
  • 1
  • 1
John Wu
  • 50,556
  • 8
  • 44
  • 80
  • Thanks for that! Am I correct in thinking I wont be able to call that from the thread that hits the error threshold? I'm thinking maybe use a global boolean that is constantly checked during runtime and if it changes the thread closes itself as suggested in the post you've linked. – Muz Feb 22 '17 at 02:03
  • 1
    Probably better to manage the list from a thread that isn't in the list itself. Otherwise the code could end up aborting itself!!! – John Wu Feb 22 '17 at 02:04
  • 1
    No, no, no! Never call `Thread.Abort()`. It can very easily corrupt the .NET runtime. It should only ever be used when you want to force an entire application to shutdown. – Enigmativity Feb 22 '17 at 02:21
  • Or if you really hate kittens. – John Wu Feb 22 '17 at 02:23