线程简介


Thread

目录:

  • 目录:
  • 1 线程基础的简单介绍
  • 2 线程同步与线程异步的简单介绍
  • 3 前台线程与后台线程的简单介绍
  • 4 细说下Thread 最为关键的构造函数
  • 5 细说下Thread 的 Sleep方法
  • 6 细说下Thread 的 join 方法
  • 7 细说下Thread 的 Abort和 Interrupt方法
  • 8 细说下Thread 的 Suspend,Resume方法
  • 9 简单了解下Thread 的 一些重要属性
  • 10  简单示例
  •      多线程从一个图片中截取部分图片
  • 11 本章总结

 

 

 

复制代码

 public class ThreadStartTest 
    {
        //无参数的构造函数
        Thread thread = new Thread(new ThreadStart(ThreadMethod));
        //带有object参数的构造函数
        Thread thread2 = new Thread(new ParameterizedThreadStart(ThreadMethodWithPara));
        public ThreadStartTest() 
        {
            //启动线程1
            thread.Start();
            //启动线程2
            thread2.Start(new Parameter { paraName="Test" });
        }
        static void ThreadMethod() 
        {
           //....
        }
        static void ThreadMethodWithPara(object o) 
        {
            if (o is Parameter) 
            {
               // (o as Parameter).paraName.............
            }
        }


    }

    public class Parameter 
    {
        public string paraName { get; set; }
    }
复制代码

不带参数的方法似乎很简单的能被调用,只要通过第一个构造函数便行,对于带参数的方法,大家注意下参数是如何传入线程所调用的方法,

当启动线程时,参数通过thread.Start方法传入,于是我们便成功启动了thread线程,大伙可千万不要小看基础啊,往往在复杂的项目中很多

就是因为一些基础导致,所以一定不要忽视它。。。

 

复制代码

     public static void ShowFatherAndSonThread(Thread grandFatherThread)
        {
            Console.WriteLine("爷爷主线程名:{0}", grandFatherThread.Name);
            Thread brotherThread = new Thread(new ThreadStart(() => { Console.WriteLine("兄弟线程名:{0}", Thread.CurrentThread.Name); }));
            Thread fatherThread = new Thread(new ThreadStart(
                () =>
                {
                    Console.WriteLine("父亲线程名:{0}", Thread.CurrentThread.Name);
                    Thread sonThread = new Thread(new ThreadStart(() =>
                    {
                        Console.WriteLine("儿子线程名:{0}", Thread.CurrentThread.Name);
                    }));
                    sonThread.Name = "SonThread";
                    sonThread.Start();
                }
                    ));
            fatherThread.Name = "FatherThread";
            brotherThread.Name="BrotherThread";
            fatherThread.Start();
            brotherThread.Start();
        }
复制代码

言归正传让我们温故下Jion方法,先看msdn中是怎么解释的:

继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻塞调用线程,直到某个线程终止为止。

大家把注意力移到后面红色的部分,什么是“调用线程”呢?如果你理解上述线程关系的话,可能已经理解了,主线程(爷爷辈)的调用了父亲线程,

父亲线程调用了儿子线程,假设现在我们有一个奇怪的需求,必须开启爷爷辈和父亲辈的线程但是,爷爷辈线程必须等待父亲线程结束后再进行,

这该怎么办? 这时候Join方法上场了,我们的目标是阻塞爷爷线程,那么后面的工作就明确了,让父亲线程(thread)对象去调用join方法就行

一下是个很简单的例子,让大家再深入理解下。

复制代码
        public static void ThreadJoin()
        {
            Console.WriteLine("我是爷爷辈线程,子线程马上要来工作了我得准备下让个位给他。");
            Thread t1 = new Thread(
                new ThreadStart
                    (
                     () =>
                     {
                         for (int i = 0; i < 10; i++)
                         {
                             if (i == 0)
                                 Console.WriteLine("我是父亲线层{0}, 完成计数任务后我会把工作权交换给主线程", Thread.CurrentThread.Name);
                             else
                             {
                                 Console.WriteLine("我是父亲线层{0}, 计数值:{1}", Thread.CurrentThread.Name, i);
                             }
                             Thread.Sleep(1000);
                         }
                     }
                    )
                );
            t1.Name = "线程1";
            t1.Start();
            //调用join后调用线程被阻塞
t1.Join(); Console.WriteLine(
"终于轮到爷爷辈主线程干活了"); }
复制代码

代码中当父亲线程启动后会立即进入Jion方法,这时候调用该线程爷爷辈线程被阻塞,直到父亲线程中的方法执行完毕为止,最后父亲线程将控制

权再次还给爷爷辈线程,输出最后的语句。聪明的你肯定会问:兄弟线程怎么保证先后顺序呢?很明显如果不使用join,一并开启兄弟线程后结果

是随机的不可预测的(暂时不考虑线程优先级),但是我们不能在兄弟线程全都开启后使用join,这样阻塞了父亲线程,而对兄弟线程是无效的,

其实我们可以变通一下,看以下一个很简单的例子:

复制代码
        public static void ThreadJoin2()
        {
            IList threads = new List();
            for (int i = 0; i < 3; i++)
            {
                Thread t = new Thread(
                    new ThreadStart(
                        () =>
                        {

                            for (int j = 0; j < 10; j++)
                            {
                                if (j == 0)
                                    Console.WriteLine("我是线层{0}, 完成计数任务后我会把工作权交换给其他线程", Thread.CurrentThread.Name);
                                else
                                {
                                    Console.WriteLine("我是线层{0}, 计数值:{1}", Thread.CurrentThread.Name, j);
                                }

                                Thread.Sleep(1000);
                            }
                        }));
                t.Name = "线程" + i;
                //将线程加入集合
                threads.Add(t);
            }

            foreach (var thread in threads)
            {
                thread.Start();
                //每次按次序阻塞调用次方法的线程
                thread.Join();
            }
        }
复制代码

 输出结果:

但是这样我们即便能达到这种效果,也会发现其中存在着不少缺陷:

1:必须要指定顺序

2:一旦一个运行了很久,后续的线程会一直等待很久

3: 很容易产生死锁

从前面2个例子能够看出 jion是利用阻塞调用线程的方式进行工作,我们可以根据需求的需要而灵活改变线程的运行顺序,但是在复杂的项目或业务中

对于jion方法的调试和纠错也是比较困难的。

 

复制代码

  static void Main(string[] args)
        {
            try
            {
                Thread.CurrentThread.Abort();
            }
            catch
            {
                //Thread.ResetAbort();
                Console.WriteLine("主线程接受到被释放销毁的信号");
                Console.WriteLine( "主线程的状态:{0}",Thread.CurrentThread.ThreadState);
            }
            finally
            {
                Console.WriteLine("主线程最终被被释放销毁");
                Console.WriteLine("主线程的状态:{0}", Thread.CurrentThread.ThreadState);
                Console.ReadKey();
            }
}
复制代码

从运行结果上看很容易看出当主线程被终止时其实报出了一个ThreadAbortException, 从中我们可以进行捕获,但是注意的是,主线程直到finally语

句块执行完毕之后才真正结束(可以仔细看下主线程的状态一直处于AbortRequest),如果你在finally语句块中执行很复杂的逻辑或者计算的话,那

么只有等待直到运行完毕才真正销毁主线程(也就是说主线程的状态会变成Aborted,但是由于是主线程所以无法看出).

 

2 尝试终止一个子线程

同样先看下代码:

复制代码
static void TestAbort() 
        {
            try
            {
                Thread.Sleep(10000);
            }
            catch 
            {
                Console.WriteLine("线程{0}接受到被释放销毁的信号",Thread.CurrentThread.Name);
                Console.WriteLine("捕获到异常时线程{0}主线程的状态:{1}", Thread.CurrentThread.Name,Thread.CurrentThread.ThreadState);
            }
            finally
            {
                Console.WriteLine("进入finally语句块后线程{0}主线程的状态:{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
            }
        }

Main:
static void Main(string[] args)
        {
         
            Thread thread1 = new Thread(TestAbort);
            thread1.Name = "Thread1";
            thread1.Start();
            Thread.Sleep(1000);
            thread1.Abort();
            thread1.Join();
            Console.WriteLine("finally语句块后,线程{0}主线程的状态:{1}", thread1.Name, thread1.ThreadState);
            Console.ReadKey();
        }
复制代码

 

了解了主线程的销毁释放后,再来看下子线程的销毁释放的过程(Start->abortRequested->Aborted->Stop),从最后输出的状态变化来看,

子线程thread1 的状态变化是十分清楚的,几乎和主线程的例子一致,唯一的区别是我们在 main方法中故意让主线程阻塞这样能看见thread 1

在 finally语句块后的状态

3,尝试对尚未启动的线程调用Abort

如果对一个尚未启动的线程调用Abort的话,一旦该线程启动就被停止了

4  尝试对一个挂起的线程调用Abort

Abort is called on a thread that has been suspended, a ThreadStateException is thrown in the thread that called Abort, and AbortRequested is added to the ThreadState property of the thread being aborted." data-guid="e0085875bc00667df94cf19fdc8e4ce9">如果在已挂起的线程上调用 Abort则将在调用 Abort 的线程中引发 ThreadStateException,并将 AbortRequested 添加到被中止的线程的

Abort is called on a thread that has been suspended, a ThreadStateException is thrown in the thread that called Abort, and AbortRequested is added to the ThreadState property of the thread being aborted." data-guid="e0085875bc00667df94cf19fdc8e4ce9">ThreadState 属性中。ThreadAbortException is not thrown in the suspended thread until Resume is called." data-guid="1c4605c458275a4ef363e92972870795">直到调用 Resume 后才在挂起的线程中引发 ThreadAbortExceptionAbort is called on a managed thread while it is executing unmanaged code, a ThreadAbortException is not thrown until the thread returns to managed code." data-guid="1e804c836f85431716b33177bff35e60">如果在正在执行非托管代码的托管线程上调用 Abort

Abort is called on a managed thread while it is executing unmanaged code, a ThreadAbortException is not thrown until the thread returns to managed code." data-guid="1e804c836f85431716b33177bff35e60">则直到线程返回到托管代码才引发 ThreadAbortException

 

 Interrupt 方法:

Interrupt 方法将当前的调用该方法的线程处于挂起状态,同样在调用此方法的线程上引发一个异常:ThreadInterruptedException和Abort方法不同的是,

被挂起的线程可以唤醒

复制代码
        static void Main(string[] args)
        {
            Thread thread1 = new Thread(TestInterrupt);
            thread1.Name = "Thread1";
            thread1.Start();
            Thread.Sleep(1000);
            thread1.Interrupt();
            thread1.Join();
            Console.WriteLine("finally语句块后,线程{0}主线程的状态:{1}", thread1.Name, thread1.ThreadState);
            Console.ReadKey();
        }
        static void TestInterrupt() 
        {
            try
            {
                Thread.Sleep(3000);
            }
            catch (ThreadInterruptedException e)
            {
                Console.WriteLine("线程{0}接受到被Interrupt的信号", Thread.CurrentThread.Name);
                Console.WriteLine("捕获到Interrupt异常时线程{0}的状态:{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
            }
            finally 
            {
                Console.WriteLine("进入finally语句块后线程{0}的状态:{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
            }
        }
复制代码

从代码中可以看出,当线程调用Interrupted后,它的状态是已中断的.这个状态对于正在执行join,sleep的线程,却改变了线程的运行结果

.因为它正在某一对象的休息室中,这时如果它的中断状态被改变,那么它就会抛出ThreadInterruptedException异常,意思就是这个线程不能再等待了,其意义就等同于唤醒它了。

让我们想象一下我们将一个线程设置了其长达1星期的睡眠时间,有时后必须唤醒它,上述方法就能实现这点

 

 

 

 

复制代码

            Thread thread1 = new Thread(TestSuspend);
            Thread thread2 = new Thread(TestSuspend);
            thread1.Name = "Thread1";
            thread2.Name = "Thread2";
            thread1.Start();
            thread2.Start();
            //假设在做一些事情
            
            Thread.Sleep(1000);
            Console.WriteLine("需要主线程帮忙了");
// throw new NullReferenceException("error!"); thread1.Resume(); thread2.Resume(); static void TestSuspend() { Console.WriteLine("Thread:{0} has been suspend!",Thread.CurrentThread.Name); //这里讲当前线程挂起 Thread.CurrentThread.Suspend(); Console.WriteLine("{0} has been resume", Thread.CurrentThread.Name); }
复制代码

如上代码,我们制造两个线程来实现Suspend和Resume的测试,(暂时不考虑临界区共享同步的问题),TestSuspend方法便是两个线程的共用方法,

方法中我们获取当前运行该方法的线程,然后将其挂起操作,那么假设线程1先挂起了,线程1被中止当前的工作,面壁思过去了,可是这并不影响线程

2的工作,于是线程2也急匆匆的闯了进来,结果和线程1一样的悲剧,聪明的你肯定会问,谁能让线程1和线程2恢复工作?其实有很多方法能让他们恢

复工作,但是个人认为,在不创建新线程的条件下,被我们忽视的主线程做不住了,看到自己的兄弟面壁,心里肯定不好受,于是做完他自己的一系列

事情之后,他便去召唤这2个兄弟回来工作了,可是也许会有这种情况,主线程迫于自己的事情太多太杂而甚至报出了异常, 那么完蛋了,这两个线程永

远无法继续干活了,或者直接被回收。。。

这样这次把他们共享区上锁,上面部分的代码保持不变,这样会发生什么情况呢?

复制代码
      static void TestSuspend() 
        {
            lock (lockObj)
            {
             。。。。
            }
        } 
复制代码

 (由于在TestSuspend方法中加入了锁,所以每次只允许一个线程工作,大伙不必在本文中深究锁机制,后续章节会给大家详细温故下)

尽然在thread2.resume()方法上报错了,仔细分析后发现在thread1离开共享区(testSuspend)方法之后刹那间,thread2进来了,与此同时,主线程

跑的太快了,导致thread2被挂起前去唤醒thread2,悲剧就这么发生了,其实修改这个bug很容易,只要判断下线程的状态,或者主线程中加一个Thread.Sleep()等等,

但是这种错误非常的严重,往往在很复杂的业务里让你发狂,所以微软决定放弃这两个方法,将他们归为过时方法,最后让大家看下微软那个深奥的解释,

相信看完上述例子后大家都能理解这个含义了

 

 

复制代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Drawing;
using System.Windows.Interop;
using System.Threading;

namespace ImageFlip
{
    /// 
    /// WPF  多线程将图片分割
    /// 
    public partial class MainWindow : Window
    {
        BitmapSource source;
        private object lockObj = new object();
        public MainWindow()
        {
            InitializeComponent();
            //首先获取图片
            Bitmap orginalImage = new Bitmap(@"G:\Picture\Tamriel_4E.png");
            //创建线程1
            Thread t1 = new Thread(new ParameterizedThreadStart
                (
                  obj =>
                  {
                      //WPF中使用多线程的话最后一定要返回UI线程,否则操作界面控件时会报错
                      //BeginInvoke方法便是返回UI线程的方法
                      this.Dispatcher.BeginInvoke((Action)(() => 
                      {
                          //通过Parameter类的属性裁剪图片
                          ClipImageAndBind(obj); 
                          //图片的部分绑定到页面控件
                          this.TestImage1.Source = source;
                          
                      }));
                  }
                ));
            //创建线程2
            Thread t2 = new Thread(new ParameterizedThreadStart
            (
              obj =>
              {
                  //WPF中使用多线程的话最后一定要返回UI线程,否则操作界面控件时会报错
                  //BeginInvoke方法便是返回UI线程的方法
                  this.Dispatcher.BeginInvoke((Action)(() =>
                  {
                      //通过Parameter类的属性裁剪图片
                      ClipImageAndBind(obj);
                      //图片的部分绑定到页面控件
                      this.TestImage2.Source = source;
                      //尝试将线程1的启动逻辑放在线程2所持有的方法中
                     // t1.Start(new Parameter { OrginalImage = orginalImage, ClipHeight = 500, ClipWidth = 500, StartX = 0, StartY = 0 });
                  }));
              }
            ));
         
            t2.Start(new Parameter { OrginalImage = orginalImage, ClipHeight = 500, ClipWidth = 500, StartX = orginalImage.Width - 500, StartY = orginalImage.Height - 500 });
            //尝试下注释掉t2.join方法后是什么情况,其实注释掉之后,两个线程会一起工作,
            //去掉注释后,界面一直到两个图片部分都绑定完成后才出现
            //t2.Join();
            t1.Start(new Parameter { OrginalImage = orginalImage, ClipHeight = 500, ClipWidth = 500, StartX = 0, StartY = 0 });
        }

       /// 
       /// 根据参数类进行剪裁图片,加锁防止共享资源被破坏
       /// 
        /// Parameter类对象
        private void ClipImageAndBind(object para)
        {
            lock (lockObj)
            {
                Parameter paraObject = (para as Parameter);
                source = this.ClipPartOfImage(paraObject);
                Thread.Sleep(5000);
            }
        }

        /// 
        /// 具体裁剪图片,大家不必在意这个方法,关键是线程的使用
        /// 
        /// Parameter
        /// 部分图片
        private BitmapSource ClipPartOfImage(Parameter para)
        {
            if (para == null) { throw new NullReferenceException("para 不能为空"); }
            if (para.OrginalImage == null) { throw new NullReferenceException("OrginalImage 不能为空"); }
            System.Drawing.Rectangle rect = new System.Drawing.Rectangle(para.StartX, para.StartY, para.ClipWidth, para.ClipHeight);
            var bitmap2 = para.OrginalImage.Clone(rect, para.OrginalImage.PixelFormat) as Bitmap;
            return ChangeBitmapToBitmapSource(bitmap2);
        }

        private BitmapSource ChangeBitmapToBitmapSource(Bitmap bmp)
        {
            BitmapSource returnSource;
            try
            {
                returnSource = Imaging.CreateBitmapSourceFromHBitmap(bmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
            }
            catch
            {
                returnSource = null;
            }
            return returnSource;
        }

    }
    
    /// 
    /// 参数类
    /// 
    public class Parameter
    {
        public Bitmap OrginalImage { get; set; }
        public int StartX { get; set; }
        public int StartY { get; set; }
        public int ClipWidth { get; set; }
        public int ClipHeight { get; set; }
    }

}
复制代码

前台界面:

复制代码
<Window x:Class="ImageFlip.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition>ColumnDefinition>
            <ColumnDefinition>ColumnDefinition>
        Grid.ColumnDefinitions>
        <Image x:Name="TestImage1" Grid.Column="0">Image>
        <Image x:Name="TestImage2" Grid.Column="1">Image>
    Grid>
Window>
复制代码

 http://www.360doc.com/content/13/1011/11/14187917_320530487.shtml