SoFunction
Updated on 2025-03-06

C# multithreading threading basics

1. Introduction

1. In order to prevent an application from controlling the CPU and causing other applications and operating systems themselves to be suspended forever, the operating system has to use some way to split the physical calculations into some virtual processes and give each executor a certain amount of computing power. In addition, the operating system must always be able to access the CPU first and be able to adjust the priority of accessing the CPU from different programs. The realization of this concept of threading formally.

2. Advantages and disadvantages of multi-threading:
Advantages of multithreading: multiple computing tasks can be executed at the same time, which may improve the processing power of the computer, so that the computer can execute more and more commands per second.
Disadvantages of multithreading: consumes a lot of operating system resources. Sharing a processor by multiple threads will cause the operating system to be busy managing these threads and fail to run the program.

2. Create thread

class Program
    {
        static void Main(string[] args)
        {
            Thread t1 = new Thread(new ThreadStart(PrintNumbers));//Delegate without parameters, use the method reference as a parameter            ();

            Thread t2 = new Thread(new ParameterizedThreadStart(PrintNumbers));//Delegate with parameters, use the method reference as a parameter            (10);
            ();
        }

        static void PrintNumbers()
        {
            ("...");
            for (int i = 0; i < 10; i++)
            {
                (i);
            }
        }

        //Note: To use ParameterizedThreadStart, the defined parameters must be object        static void PrintNumbers(object count)
        {
            ("...");
            for (int i = 0; i < Convert.ToInt32(count); i++)
            {
                (i);
            }
        }
    }
}

Notes:

1. We only need to specify the method names that run on different threads, and the C# compiler will create these objects in the background.

2. To use ParameterizedThreadStart, the defined parameters must be of object type.

3. Pause thread

class Program
    {
        static void Main(string[] args)
        {
            Thread t1 = new Thread(PrintNumbersWithDelay);
            ();
            PrintNumbers();
            ();
        }

        static void PrintNumbers()
        {
            ("...");
            for (int i = 0; i < 10; i++)
            {
                ("In : " + i);
            }
        }

        static void PrintNumbersWithDelay()
        {
            ("...");
            for (int i = 0; i < 10; i++)
            {
                //var a = (2);
                ((2));//Pause for two seconds                ("In : " + i);
            }
        }
    }

Note: Use ((2)); Pause the thread for a period of time

4. Thread waiting

class Program
    {
        static void Main(string[] args)
        {
            ("Starting...");
            Thread t = new Thread(PrintNumbersWithDelay);
            ();
            ();   // Use Join to wait for t to complete, then execute PrintNumbers down. If the comment is done, the output is obviously different            PrintNumbers();
            ("Thread Complete");
            ();
        }

        static void PrintNumbers()
        {
            ("...");
            for (int i = 0; i < 10; i++)
            {
                ("In :" + i);
            }
        }

        static void PrintNumbersWithDelay()
        {
            ("...");
            for (int i = 0; i < 10; i++)
            {
                ((2));
                ("In :" + i);
            }
        }
    }

Comment: Use ();   Wait for t to complete.

V. Terminate thread

class Program
    {
        static void Main(string[] args)
        {
            ("Starting Program...");
            Thread t1 = new Thread(PrintNumbersWithDelay);
            ();
            ((7));// At this time, the t1 thread will execute for 7 seconds            ();    //Use Abort() to terminate the thread            ("Thread t1 has been aborted");
            Thread t2 = new Thread(PrintNumbers);
            ();
            ();
        }

        static void PrintNumbers()
        {
            ("...");
            for (int i = 0; i < 10; i++)
            {
                ("In :" + i);
            }
        }
        static void PrintNumbersWithDelay()
        {
            ("...");
            for (int i = 0; i < 10; i++)
            {
                ((2));
                ("In :" + i);
            }
        }
    }

Note: Use the Abort method of the Thread instance to terminate the thread.

6. Detect thread status

class Program
    {
        static void Main(string[] args)
        {
            ("Start Program...");
            Thread t1 = new Thread(PrintNumbersWithStatus);
            Thread t2 = new Thread(DoNothing);
            ("t1 status:" + ());//Get the instance thread status            ();
            ();
            for (int i = 0; i < 30; i++)
            {
                ("t1 status:" + () + "\t" + "t2 status:" + ());
            }
            ((7));
            ();
            ("thread t1 has been aborted");
            ("t1 status:" + ());
            ("t2 status:" + ());
            ();
        }

        private static void PrintNumbersWithStatus()
        {
            ("...");
            ("In  t1 status:" + ());//Get the current thread state            for (int i = 0; i < 10; i++)
            {
                ((2));
                ("In :" + i);
            }
        }

        private static void DoNothing()
        {
            ((2));
            ("t2 Console...");
        }
    }

Note: Use get the running status of the thread. ThreadState is a C# enum. Remember: Do not use thread termination in your program, otherwise unexpected results may occur.

7. Thread priority

class Program
    {
        static void Main(string[] args)
        {
            //Let all threads of the operating system run on multiple CPU cores            ($"Current thread priority: {}");
            ("Running on all cores available");//Get the instance thread status            RunThreads();

            ((2));
            ("Running on a single Core");
            //Let all threads of the operating system run on a single CPU core            ().ProcessorAffinity = new IntPtr(1);
            RunThreads();
            ();
        }

        private static void RunThreads()
        {
            var sample = new ThreadSample();
            var t1 = new Thread();
             = "Thread One";
            var t2 = new Thread();
             = "Thread Two";

             = ;// Use Priority to set the priority of thread             = ;
            ();
            ();// Here t2 has a priority lower than t1, t2 waits for t1 to release the resource.
            ((2));
            ();
        }
    }

    class ThreadSample
    {
        private bool _isStopped = false;
        public void Stop()
        {
            _isStopped = true;
        }
        public void CountNumbers()
        {
            long counter = 0;
            while (!_isStopped)
            {
                counter++;
            }
            ($"{} with {} priority has a count={("N0")}");
        }
    }

Note: It takes a lot more time to execute multithreading for a single core than for a multi-core.

8. Foreground threads and background threads

class Program
    {
        static void Main(string[] args)
        {
            var sampleForground = new ThreadSample(10);
            var sampleBackground = new ThreadSample(20);
            var t1 = new Thread();//Reference of method             = "ForegroundThread"; //All foreground threads that are not explicitly declared are foreground threads
            var t2 = new Thread();
             = "BackgroundThread";
             = true;   //Set as background thread
            ();
            ();
            ();

        }
    }
    class ThreadSample
    {
        private readonly int _iteration;

        public ThreadSample(int iteration)
        {
            _iteration = iteration;
        }

        public void CountNumbers()
        {
            for (int i = 0; i < _iteration; i++)
            {
                ((0.5));
                ($"{} prints {i}");
            }
        }
    }

Note: The process will wait for all foreground threads to complete before ending work, but if only background threads are left, the work will be completed directly.

9. Pass parameters to threads

class Program
    {
        static void Main(string[] args)
        {
            ThreadSample sample = new ThreadSample(5);
            Thread t1 = new Thread();
             = "ThreadOne";
            ();
            ();
            ("--------------------------");

            Thread t2 = new Thread(Count);
             = "ThreadTwo";
            (3);
            ();
            ("--------------------------");

            //C# The other party's way is called a closure. When using any local variable in a lambda expression, C# will generate a class and use the variable as a property of the class, but we do not need to define the class, the C# compiler will automatically implement it for us. Thread t3 = new Thread(() => CountNumbers(5));             = "ThreadThree";
            ();
            ();
            ("--------------------------");

            int i = 10;
            Thread t4 = new Thread(() => PrintNumber(i));

            i = 20;
            Thread t5 = new Thread(() => PrintNumber(i));
            ();
            ();
            //t4 and t5 will output 20, because t4 and t5 have already become 20 before Start            ();
        }

        static void Count(object iterations)
        {
            CountNumbers((int)iterations);
        }

        static void CountNumbers(int iterations)
        {
            for (int i = 1; i <= iterations; i++)
            {
                ((0.5));
                ($"{} prints {i}");
            }
        }

        static void PrintNumber(int number)
        {
            (number);
        }
    }
    class ThreadSample
    {
        private readonly int _iteration;

        public ThreadSample(int iteration)
        {
            _iteration = iteration;
        }

        public void CountNumbers()
        {
            for (int i = 1; i <= _iteration; i++)
            {
                ((0.5));
                ($"{} prints {i}");
            }
        }
    }

10. Use C# Lock keywords

class Program
    {
        static void Main(string[] args)
        {
            ("Incorrect Counter");
            Counter c1 = new Counter();
            var t1 = new Thread(() => TestCounter(c1));
            var t2 = new Thread(() => TestCounter(c1));
            var t3 = new Thread(() => TestCounter(c1));
            ();
            ();
            ();
            ();
            ();
            ();
            ($"Total Count: {}");
            ("------------------------");

            //Use the LOCK keyword, only one thread is allowed to access Count at the same time            ("Correct counter");
            CounterWithLock c2 = new CounterWithLock();
            t1 = new Thread(() => TestCounter(c2));
            t2 = new Thread(() => TestCounter(c2));
            t3 = new Thread(() => TestCounter(c2));
            ();
            ();
            ();
            ();
            ();
            ();
            ($"Total count:{}");
            ();
        }

        static void TestCounter(CounterBase c)
        {
            for (int i = 0; i < 100000; i++)
            {
                ();
                ();
            }
        }

        //Subclass        class Counter : CounterBase
        {
            public int Count { get; private set; }
            //Rewrite base class method            public override void Decrement()
            {
                Count--;
            }

            public override void Increment()
            {
                Count++;
            }
        }

        //Subclass        class CounterWithLock : CounterBase
        {
            private readonly object _asyncRoot = new object();
            public int Count { get; private set; }
            //Rewrite base class method            public override void Decrement()
            {
                lock (_asyncRoot)
                {
                    Count--;
                }
            }

            public override void Increment()
            {
                lock (_asyncRoot)
                {
                    Count++;
                }
            }
        }

        //Basic Class        abstract class CounterBase
        {
            public abstract void Increment();

            public abstract void Decrement();
        }
    }
    class ThreadSample
    {
        private readonly int _iteration;

        public ThreadSample(int iteration)//Constructor        {
            _iteration = iteration;
        }

        public void CountNumbers()
        {
            for (int i = 1; i <= _iteration; i++)
            {
                ((0.5));
                ($"{} prints {i}");
            }
        }
    }

Note: Without locking, the result is uncertain and errors are prone to competition conditions. The result of locking is correct, but performance is affected

11. Use the Monitor class to lock resources

class Program
    {
        static void Main(string[] args)
        {
            object lock1 = new object();
            object lock2 = new object();
            new Thread(() => LockTooMuch(lock1, lock2)).Start();
            lock (lock2)
            {
                (1000);
                (" allows not to get stuck, returning false after a specified timeout is elapsed");
               
                //Use directly, if the lock-protected resource has not been obtained before the second parameter, it will return false                if ((lock1, (5)))
                {
                    ("Acquired a protected resource successfully");
                }
                else
                {
                    ("Timeout acquiring a resource");
                }
            }
            new Thread(() => LockTooMuch(lock1, lock2)).Start();
            ("-----------------------------");
         
            /* The following code will cause a deadlock, so comment out
             lock (lock2)
             {
                 ("This will be a deadlock!");
                 (1000);
                 lock (lock1)
                 {
                     ("Acquired a protected resource successfully");
                 }
             }
             */
        }

        static void LockTooMuch(object lock1, object lock2)
        {
            lock (lock1)
            {
                (1000);
                lock (lock2);
            }
        }
    }

Note: Attempt to obtain the exclusive lock on the specified object within the specified time

12. Handle exceptions

class Program
    {
        static void Main(string[] args)
        {
            Thread t = new Thread(FaultyThread);
            ();
            ();
            try
            {
                t = new Thread(BadFaultyThread);
                ();
            }
            catch (Exception ex)
            {
                ("We won't get here");
            }
        }
        static void BadFaultyThread()
        {
            ("Starting a bad faulty thread.....");
            ((2));
            //The main thread cannot catch this exception because it is an exception thrown in the child thread.  Need to add try...catch to catch exceptions in the child thread            throw new Exception("Boom!");
        }
        static void FaultyThread()
        {
            try
            {
                ("Starting a faulty thread...");
                ((1));
                throw new Exception("Boom!");
            }
            catch (Exception ex)
            {
                ($"Exception handled: {}");
            }
        }
    }

This is all about this article about the threading basics of c# multithreading. I hope it will be helpful to everyone's learning and I hope everyone will support me more.