【C#多线程】1.Thread类的使用及注意要点

Thread随便讲讲

  由于在C#中,Thread类在咱们的新业务上并不经常使用了(由于建立一个新线程要比直接从线程池拿线程更加耗费资源),而且在.NET4.0后新增了Task类即Async与await关键字,使得咱们基本再也不用Thread了,不过在学习多线程前,有必要先了解下Thread类,这里就先随便讲讲Thread。前端

1.使用多线程的几种方式

  多线程Thread类只支持运行两种方法,一种是无参数而且无返回值的方法,第二种是有一个Object类型参数(有且只能有一个参数,而且必须是Object类型)且无返回值的方法。若是想让多线程方法携带多个参数,能够将多个参数放入一个集合或数组中传入方法。数组

  下面例子使用了控制台来演示多线程的简单使用:多线程

using System; using System.Threading; namespace ConsoleApplication1 { class Program { //无参数无返回值方法
        public static void DoSomething() { for (int i = 0; i < 100; i++) { Thread.Sleep(500); } } //有参数无返回值方法
        public static void DoSomethingWithParameter(object obj) { for (int i = 0; i < (int)obj; i++) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Thread.Sleep(500); } } static void Main(string[] args) { //获取主线程ID
            int currentThreadId = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"---------主线程<{currentThreadId}>开始运行---------"); //多线程运行无参数方法方式1
            ThreadStart ts = DoSomething;//ThreadStart是一个无参数,无返回值的委托
            Thread thread1 = new Thread(ts); thread1.Start(); //多线程运行无参数方法方式2
            Thread thread2 = new Thread(DoSomething);//可省略ThreadStart
 thread2.Start(); //多线程运行有参数方法方式1 //ParameterizedThreadStart是一个有一个Object类型参数,可是无返回值的委托。
            ParameterizedThreadStart pts = DoSomethingWithParameter; Thread thread3 = new Thread(pts); thread3.Start(100); //多线程运行有参数方法方式2 //能够省略ParameterizedThreadStart
            Thread thread4 = new Thread(DoSomethingWithParameter); thread4.Start(100); //还可使用lamda表达式简化多线程写法
            new Thread(() => { for (int i = 0; i < 100; i++) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Thread.Sleep(500); } }).Start(); Console.WriteLine($"---------主线程<{currentThreadId}>运行结束---------"); } } }

  运行结果以下:异步

  

 

 2.前台线程与后台线程

  • 前台线程

  如主线程(或称为UI线程)就是前台线程,默认Thread的实例均为前台线程,前台线程的特色是,若是当前应用的前台线程没有所有运行完毕,那么当前应用就没法退出。举个例子,咱们知道正常状况下,控制台应用在Main方法结束后会自动结束当前进程,若是咱们在Main方法中建立了一个新Thread线程,并使其保持运行,那么即便Main方法执行完毕,控制台进程也没法自动关闭(除非手动右上角点×)。就以下图状况,画红圈的地方表示Main方法执行完毕,但是程序依旧在运行,因此咱们通常在用Thread的时候会将Thread设置为后台线程。学习

 

  • 后台线程

  后台线程与前台线程的惟一区别是,它不会去影响程序的生老病死,当程序的前台线程所有关闭(即程序退出),那么即便程序的后台线程依旧在执行任务,那么也会强制关闭。this

  设置Thread为后台线程的方式:spa

        Thread tt = new Thread(DoSomething); tt.IsBackground = true;//设置tt为后台线程
        tt.Start();

  前台线程与后台线程对程序的影响效果看似好像不算大,可是若是咱们在作Winform或者WPF项目时,若在某窗体内执行一个新线程任务(这个新线程是前台线程),若是在任务执行期间关闭程序,此时会发现,虽然界面都被关闭,可是计算机任务管理器中此程序依旧还在运行(而且若是在新线程中执行的任务异常致使线程没法关闭,那么这个程序就会一直在后台跑下去),再次开启程序可能会致使打不开等后果,这种行为是很是很差的。因此咱们通常使用多线程Thread类时,最好顺手将它设置为后台线程。咱们能够举个例子。线程

        static void Main(string[] args) { //获取主线程ID
            int currentThreadId = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"---------主线程<{currentThreadId}>开始运行---------"); //执行一个大概能够运行50秒的新线程
            Thread t = new Thread(() => { for (int i = 0; i < 100; i++) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Thread.Sleep(500); } }); t.IsBackground = true;//设置t为后台线程
 t.Start(); Console.WriteLine($"---------主线程<{currentThreadId}>运行结束---------"); }

  这个例子的运行结果就不截图了,由于控制台会一闪而过(当即执行完Main方法便关闭),即便后台线程t还在执行任务,可是也会强制关闭。code

 

3.让主线程等待新线程执行完成后再继续执行(使用Thread的Join方法)

  直接上代码:orm

        static void Main(string[] args) { //获取主线程ID
            int currentThreadId = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"---------主线程<{currentThreadId}>开始运行---------"); //执行一个大概能够运行50秒的新线程
            Thread t = new Thread(() => { for (int i = 0; i < 20; i++) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Thread.Sleep(500); } }); t.IsBackground = true;//设置t为后台线程
 t.Start(); t.Join();//在t线程执行期间,若是主线程调用t线程的Join方法,主线程会卡在这个地方直到t线程执行完毕
 Console.WriteLine($"---------主线程<{currentThreadId}>运行结束---------"); }

 

4.Thread实例的其余经常使用方法

  直接看代码注释吧:

        static void Main(string[] args) { //执行一个大概能够运行50秒的新线程
            Thread t = new Thread(DoSth); t.IsBackground = true;//设置t为后台线程
 t.Start(); t.Join();//在t线程执行期间,若是主线程调用t线程的Join方法,主线程会卡在这个地方知道t线程执行完毕
            t.Priority = ThreadPriority.Normal;//设置线程调度的优先级
            ThreadState rhreadState = t.ThreadState;//获取线程运行状态。
            bool b = t.IsAlive;//获取线程当前是否存活
            t.Interrupt();//中断当前线程
            t.Abort();//终止线程 
        }

 

5.Thread类的经常使用方法

  直接看代码注释吧:

        static void Main(string[] args) { //使得当前线程暂停1秒再继续执行,此处会暂停主线程1秒钟 //若是写在其余线程执行的方法中,会让执行那个方法的线程暂停1秒再继续执行)
            Thread.Sleep(1000); //获取当前执行线程的线程实例
            Thread t = Thread.CurrentThread; }

  

6.使用多线程须要注意的要点

   (1)子线程不能够直接调用UI线程(即主线程)的UI对象,可是能够调用在主线程自定义的对象

  咱们在作Winform或WPF开发时,例如在前端有一个TextBox文本框,其Name属性为textBox,那么若是咱们在此窗体内开启了个子线程,并在子线程内对textBox.Text赋值,是会报错的,由于子线程没法访问主线程的UI元素(实质是UI元素必须由建立它的线程去操做)。

  以下代码,子线程操做主线程建立的对象时不会报错,可是子线程操做主线程建立的UI对象时会报错:

        private void button1_Click(object sender, EventArgs e) { Student stu = new Student();//主线程建立的Student类实例
            new Thread(() => { stu.Name = "ccc";//子线程操做主线程建立的对象并不会报错。
                textBox1.Text = "abc";//子线程直接调用UI线程textBox1会报错
 }).Start(); }

 

   解决思路:在子线程想操做UI线程的UI元素时,呼叫主线程去操做便可,代码以下:

        delegate void DoSth(string str);//建立一个委托
        public void SetTextBox(string str)//建立一个委托方法用于改变主线程textBox的值
 { textBox1.Text = str; }
     //按钮点击事件
private void button1_Click(object sender, EventArgs e) {
       //在子线程内执行....
new Thread(() => { //----------------------详细写法------------------------ DoSth delegateMethod = new DoSth(SetTextBox);//建立方法的委托 //this指当前Window //this.Invoke指让建立窗体的线程执行某委托方法 //第二个参数是传入委托方法即SetTextBox的参数 this.Invoke(delegateMethod, "abc"); //----------------------简写方式---------------------- this.Invoke(new Action(() => { textBox1.Text = "abc";//子线程直接调用UI线程textBox1会报错 })); }).Start();

  补充:上面代码是Winform跨线程操做UI元素的经常使用方式,那么WPF怎么跨线程操做UI呢?直接看下面代码吧

    //方式1(经常使用):获取当前应用的UI线程,执行某方法
    App.Current.Dispatcher.Invoke(() => { textBox1.Text="abc" }); //方式2(只能在this是当前Window或能够获取到窗体实例的状况下使用):
    this.Dispatcher.Invoke(new Action(()=> { textBox1.Text="abc" })); 

  (2)多线程同时访问一个资源时,要注意同步问题。

  好比两个及以上的线程同时访问一个资源(能够是文件,能够是对象),若是没有注意同步问题,会致使如下问题。直接看代码

        private void button1_Click(object sender, EventArgs e) { int num = 0; //建立两个线程对num进行累加,各加100000,理论上线程执行完毕后最后的值应该是200000
            Thread t1 = new Thread(() => { for (int i = 0; i < 100000; i++) { num++; } }); Thread t2 = new Thread(() => { for (int i = 0; i < 100000; i++) { num++; } }); //两个子线程同时执行
 t1.Start(); t2.Start(); //等待t1与t2线程执行完毕再继续执行
 t1.Join(); t2.Join(); Console.WriteLine(num.ToString());//输出num的值 }

  结果:

  结果并非200000,由于t1与t2线程同时对num进行自增操做时候,常常会出现t1读取到了num为99,自增1,结果100赋值给num,可是在t1刚读取到num值而且还没进行自增操做时,t2也读取到了num为99,自增下也是100赋值给num。也就是说t1与t2进行了相同的操做。

  

 

   如何避免当前这个问题呢?就须要在多线程访问一个资源时,进行资源同步处理。那什么是同步呢?同步是指我用完了你才能用,你我不能同时使用一个资源。这个问题的详细解决方法会在该系列之后的博客中写。

 

  

  下节咱们会简单讲讲线程池+之前的两种异步处理机制。

相关文章
相关标签/搜索