C# 线程基础

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# 线程基础相关的知识,希望对你有一定的参考价值。

1. 线程的基本概念

简单的讲进程就是程序分配在内存当中,等待处理器进行处理,请记住线程会消耗大量的操作系统资源。多个线程共享一个物理处理器将导致处理器忙于处理管理这些进程,而无法运行程序。

使用线程通常是一个操作系统的任务,试图在一个单核CPU上并行执行计算任务是没有任何意义的,可能比顺序执行花费的时间更长。

为了更好的利用现代处理器的能力,使用多线程处理程序发挥出最好的作用,这需要组织多个线程间的通讯和相互同步。

下面将学习一下 线程的生命周期,和创建线程、挂起线程、线程等待、以及终止线程。

创建一个线程操作

        static void Main(string[] args)
        {
            //创建一个新的线程来实现输出数字
            Thread t = new Thread(PrintNumber);
            t.Start();
            //这一行代码是在创建了一个新的线程并行执行的
            PrintNumber();
            Console.ReadLine();
        }

        static void PrintNumber() 
        {
            Console.WriteLine("Starting...");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(i);
            }
        }

从上面代码可以看出一个程序始终有一个主线程在执行,而Thread是创建一个新的线程执行。两者之间是同步执行的。

暂停线程

        static void Main(string[] args)
        {
            //创建一个带暂停的进程
            Thread t = new Thread(PrintNumberWithdelay);
            t.Start();
            //这一行就是始终执行的主线程经过(一个程序都有一个始终执行的主线程)
            PrintNumber();
        }

        static void PrintNumber() 
        {
            Console.WriteLine("Starting...");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(i);
            }
        }
        static void PrintNumberWithdelay() 
        {
            Console.WriteLine("暂停...");
            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine(i);
            }
        }

很明显,主线程已经执行完毕,而新的线程在输出暂停以后还在继续执行。每次执行都会休眠2秒钟。

线程等待

那么如何不让主线程继续往下执行,而是等待新线程执行完毕再往下执行呢。

        static void Main(string[] args)
        {
            //创建一个带暂停的进程
            Thread t = new Thread(PrintNumberWithdelay);
            t.Start();
            t.Join();//线程等待
            Console.WriteLine("执行完毕了");
         
        }
        static void PrintNumberWithdelay()
        {
            Console.WriteLine("暂停...");
            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                Console.WriteLine(i);
            }
        }

终止线程

        static void Main(string[] args)
        {
            //创建一个带暂停的进程
            Thread t = new Thread(PrintNumberWithdelay);
            t.Start();
            Thread.Sleep(TimeSpan.FromSeconds(3));
            t.Abort();//终止线程
            Console.WriteLine("线程终止了");         
        }

这段代码是给线程注入了ThreadAbortException方法,导致线程被终结,这样操作是非常危险的,因为该异常可能会导致整个应用程序都崩溃。

可以使用 ResetAbort 方法来拒绝被终止。

检测线程状态

        static void Main(string[] args)
        {
            //创建一个带暂停的进程
            Thread t = new Thread(PrintNumberWithdelay);
            Thread t1 = new Thread(DoNothing);
            t1.Start();
            t.Start();
            for (int i = 0; i < 30; i++)
            {
                Console.WriteLine(t.ThreadState.ToString());
            }            
            Thread.Sleep(TimeSpan.FromSeconds(3));
            t.Abort();//终止线程
            Console.WriteLine("线程终止了");
            Console.WriteLine(t.ThreadState.ToString());
            Console.WriteLine(t1.ThreadState.ToString());
        }

        static void PrintNumberWithdelay()
        {
            Console.WriteLine("开始啦...");
            Console.WriteLine(Thread.CurrentThread.ThreadState.ToString());
            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                Console.WriteLine(i);
            }
        }
        static void DoNothing()
        {
            Thread.Sleep(TimeSpan.FromSeconds(2));
        }

当主程序定义了两个不同的线程,一个将会被终止,而另一个则会成功运行。线程状态位于Thread 对象的ThreadState属性中。ThreadState属性是一个C#枚举对象。

刚开始线程状态为Unstarted ,然后启动线程,并估计在一周期为30的迭代的区间中,线程状会从Running变为WitSleepJoin。

 

线程优先级

    class ThreadSample
    {
        private bool _isStopped = false;
        public void Stop()
        {
            _isStopped = true;
        }
        public void CountNumbers()
        {
            long counter = 0;
            while (!_isStopped)
            {
                counter++;
            }
            Console.WriteLine("{0} 和 {1,11}" + " count={2,13}",
                Thread.CurrentThread.Name,
                Thread.CurrentThread.Priority,
                counter.ToString("NO"));
        }
    }

    class Program
    {
        static void RunThreads()
        {
            var sample = new ThreadSample();
            var threadOne = new Thread(sample.CountNumbers);
            threadOne.Name = "ThreadOne";
            var threadTwo = new Thread(sample.CountNumbers);
            threadTwo.Name = "ThreadTwo";

            threadOne.Priority = ThreadPriority.Highest;//优先级较高
            threadTwo.Priority = ThreadPriority.Lowest;//优先级较低

            threadOne.Start();
            threadTwo.Start();

            Thread.Sleep(TimeSpan.FromSeconds(2));
            sample.Stop();

        }
        static void Main(string[] args)
        {
            Console.WriteLine("线程状态:{0}",Thread.CurrentThread.Priority);
            Console.WriteLine("开始");
            RunThreads();           
            Thread.Sleep(TimeSpan.FromSeconds(3));
            Console.WriteLine("模拟CPU单核计算");
            //让操作系统运行在第一个CPU第一个核心上
            Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
            RunThreads();//运行时间很长
            Console.WriteLine("线程终止了");
        }

    }

此程序只用于演示,通常中无需使用这种方式。

前台线程和后台线程

    class ThreadSample
    {
        private readonly int _iterations;
        public ThreadSample(int iterations)
        {
            _iterations = iterations;
        }
        public void CountNumbers()
        {
            for (int i = 0; i < _iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine("{0} 和 {1}",
                    Thread.CurrentThread.Name, i);
            }

        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var samppleforegroud = new ThreadSample(10);
            var sampplebackgroup = new ThreadSample(20);

            var threadOne = new Thread(samppleforegroud.CountNumbers);

            threadOne.Name = "前台";

            var threadTwo = new Thread(sampplebackgroup.CountNumbers);

            threadTwo.Name = "后台";
            threadTwo.IsBackground = true;

            threadOne.Start();
            threadTwo.Start();

        }

    }

显示创建的是前台线程, ThreadTwo是后台线程 ,通过配置第一个线程会比第二个线程先完成,前台线程如果执行完毕,那么也会把后台线程终止掉。

 

向线程传递参数

class ThreadSample
    {

        private readonly int _iterations;
        public ThreadSample(int iterations)
        {
            _iterations = iterations;
        }
        public void CountNumbers()
        {
            for (int i = 0; i < _iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine("{0} 和 {1}",
                    Thread.CurrentThread.Name, i);
            }

        }
    }

    class Program
    {
        static void Count(object i)
        {
            CountNumbers((int)i);
        }

        static void CountNumbers(int number)
        {
            Console.WriteLine(number);
        }

        static void PrintNumber(int number)
        {
            Console.WriteLine(number);
        }

        static void Main(string[] args)
        {
            var samppleforegroud = new ThreadSample(10);

            var threadOne = new Thread(samppleforegroud.CountNumbers);
            threadOne.Name = "One";
            threadOne.Start();
            threadOne.Join();
            Console.WriteLine("------------");


            var threadTwo = new Thread(Count);
            threadTwo.Name = "Two";
            threadTwo.Start(8);
            threadTwo.Join();

            var threaThree = new Thread(() => CountNumbers(12));
            threaThree.Name = "Three";
            threaThree.Start();
            threaThree.Join();

            int i = 10;
            var threaFour = new Thread(() => PrintNumber(i));
            i = 20;
            var threaFour1 = new Thread(() => PrintNumber(i));
            threaFour.Start();
            threaFour1.Start();

        }

    }

使用Lock

abstract class CountBase
    {
        public abstract void Increment();
        public abstract void Decrement();

    }

     class Counter : CountBase
    {
        public int Count { get; private set; }

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

        public override void Decrement()
        {
            Count--;
        }
    }

     class CounterWithLock : CountBase
    {
        private readonly object _syncroot = new object();

        public int Count { get; private set; }
        public override void Increment()
        {
            lock (_syncroot)
            {
                Count++;
            }
        }

        public override void Decrement()
        {
            lock (_syncroot)
            {
                Count--;
            }
        }
    }

    class Program
    {
        static void TestCouner(CountBase c)
        {
            for (int i = 0; i < 100000; i++)
            {
                c.Increment();
                c.Decrement();
            }
        }

        static void Main(string[] args)
        {
            var c = new Counter();

            var t1 = new Thread(() => TestCouner(c));
            var t2 = new Thread(() => TestCouner(c));
            var t3 = new Thread(() => TestCouner(c));

            t1.Start();
            t2.Start();
            t3.Start();
            t1.Join();
            t2.Join();
            t3.Join();

            Console.WriteLine(c.Count);

            var c1 = new CounterWithLock();

             t1 = new Thread(() => TestCouner(c));
             t2 = new Thread(() => TestCouner(c));
             t3 = new Thread(() => TestCouner(c));

             t1.Start();
             t2.Start();
             t3.Start();
             t1.Join();
             t2.Join();
             t3.Join();

             Console.WriteLine(c.Count);


        }

    }

 

使用Monitor 锁定资源

为了避免死锁,则使用Monitor 类 来避免死锁。

Monitor.TryEnter(lock1,TimeSpan.FromSeconds(5));

以上是关于C# 线程基础的主要内容,如果未能解决你的问题,请参考以下文章

工作流

工作流

C# 线程线程池Task概念+代码实践

C# 最有用的(自定义)代码片段是啥? [关闭]

c#代码片段快速构建代码

此 Canon SDK C++ 代码片段的等效 C# 代码是啥?