C#知识学习-进程与线程

线程的概念

  1. 进程就好比工厂的车间,它代表CPU所能处理的单个任务。
  2. 线程就好比车间里的工人,一个进程可以包含多个线程。
  3. 车间里的控件是共享的,比如许多房间是每个工人都能进出的,这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享空间
  4. 一个防止别人进入的简单方法,就是给门口加一把锁,先到的人锁上门,后到的看到门上锁,就在门口排队,等锁打开再进去。这就叫互斥锁,防止多个线程同时读写某块内存区域。
  5. 还有一些房间,可以同时进入N个人,比如厨房,也就是说,如果人数大于N,多出来的人就只能在外面等着,这好比某些内存区域,只能供给固定的数目的线程使用。
  6. 这样的解决方法,就是在门口挂上N把钥匙,一个人进去就拿一把钥匙,出来时就把钥匙挂回来,后面的人发现钥匙架空了,说明里面人满了,就只能在门口排队,这种做法叫信号量,用来保证多个线程不会相互冲突。

一般我们会为比较耗时的操作开启单独的线程去执行,比如下载操作。

线程开启的方式1

Action a = Method;//声明一个委托 这里用于开启线程
a.BeginInvoke(参数1,参数2,...,null,null);//开启一个新的线程去执行a所引用的方法
IAsyncResult ar = a.BeginInvoke(...);//IAsyncResult可以获取当前线程的状态
ar.IsCompleted;//判断当前线程是否执行完毕
a.EndInvoke(ar);//取得异步线程的返回值
Thread.Sleep(10);//控制子线程的检测频率
bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);//1000毫秒表示超过时间,如果等待了1000毫秒线程还没结束,会返回false,否则会返回true

线程开启的方式2

 static void Test(object filename)
        {
            Console.WriteLine("开始下载"+filename+Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(1000);
            Console.WriteLine("下载完成!");
        }
        static void Main(string[] args)
        {
            //1.直接传递方法
            Thread t = new Thread(Test);
            //创建Thread对象,这个线程并没有启动  如果传递的方法有参数,则参数必须是Object类型的
            t.Start("xxx");//开始,启动线程  通过Start方法传递参数
            Console.ReadKey();
-----------------------------------------------------------------------------------------------------------------
            //2.通过对象传递方法
            MyThread myThread = new MyThread("abc","www.123.bbs");//创建类对象,将数据传入
            Thread t = new Thread(myThread.DownLoad);//调用对象中的方法
            t.Start();//开启线程
            Console.ReadKey();
        }
    class MyThread
    {

        string filename;
        string filepath;
        public MyThread(string filename, string filepath)
        {
            this.filename = filename;
            this.filepath = filepath;
        }
        public void DownLoad()
        {
            Console.WriteLine("开始下载" + filepath + filename);
            Thread.Sleep(2000);
            Console.WriteLine("下载完成");
        }

    }

异步回调方法

Func<string, int, int> a = Test;
            a.BeginInvoke("A", 11, OnCallBack, a);//开启一个新的线程去执行委托中的方法
            //倒数第二个参数是一个委托类型的参数,表示回调函数,当线程结束时会调用这个委托指向的方法
            //倒数第一个参数用于给回调函数传递数据
            Console.ReadKey();
        }
        static void OnCallBack(IAsyncResult ar)
        {
            Func<string, int, int> a = ar.AsyncState as Func<string, int, int>;
            int res =  a.EndInvoke(ar);
            Console.WriteLine(res+"在回调函数中取得结果");
        }

使用lambda表达式(等同于上面的代码)

        static void OnCallBack(IAsyncResult ar)
        {
            Func<string, int, int> a = Test;
            a.BeginInvoke("A", 11,ar=>
            {
                int res = a.EndInvoke(ar);
                Console.WriteLine(res);
            },null);
        }

后台线程与前台线程

当只有一个前台线程在运行时,应用程序就在运行,如果多个前台线程在运行时,Main方法结束了,应用程序的进程仍然运行,直到所有前台线程完成其任务为止。

Thread t = new Thread(Test); //默认是前台线程

在默认情况下,用Thread创建的线程都是前台线程,在线程池里的线程都是后台线程。

t.IsBackground = true; //设置线程为后台线程

如果一个线程是后台线程,当应用程序关闭时,如果这个后台线程还没有执行完,则会被强制杀掉。

当所有前台线程运行完毕,如果还有后台线程运行的话,所有后台线程会被终止掉。

控制线程

1.获取线程状态(Running还是Unstarted..)当我们通过调用Thread对象的Start方法,可以创建线程,但是调用了Start方法之后,新线程不是马上进入Running状态,而是处于Unstarted状态,只有当操作系统的线程调度器选择运行这个线程,这个线程状态才会修改为Running状态。

我们使用Thread.Sleep()方法可以让当前线程休眠进入WaitSleepJoin状态。

t.Abort();//终止这个线程的执行

2.使用Thread对象的Abort()方法可以停止线程。调用这个方法,会在终止要终止的线程中抛出一个ThreadAbortException类型的异常,我们可以try catch这个异常,然后在线程结束前做一些清理的工作。

t.Jion();//让当前线程睡眠,等待t线程执行完,再继续执行当前线程

3.如果需要等待线程的结束,可以调用Thread对象的Join方法,表示把Thread加入进来,停止当前线程,并把它设置为WaitSleepJoin状态,直到加入的线程完成为止。

线程池

创建线程需要时间。如果有不同的小任务要完成,就可以事先创建许多线程 , 在应完成这些任务时发出请求。

使用线程池需要注意的事项:

  1. 线程池中的所有线程都是后台线程 。 如果进程的所有前台线程都结束了,所有的后台线程就会停止。 不能把入池的线程改为前台线程 。
  2. 不能给入池的线程设置优先级或名称。
  3. 入池的线程只能用于时间较短的任务。 如果线程要一直运行(如 Word的拼写检查器线程),就应使用Thread类创建一个线程。
ThreadPool.QueueUserWorkItem(Test,"种子1号");//开启一个工作线程  这个Test方法必须有一个参数  后面的是传递给Test方法的参数

例子:

  static void Test(object filename)
        {
            Console.WriteLine("开始下载"+filename+Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(2000);
            Console.WriteLine("下载完成!" + Thread.CurrentThread.ManagedThreadId);
        }
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(Test,"种子1号");//发起一个工作线程队列
            ThreadPool.QueueUserWorkItem(Test,"种子2号");
            ThreadPool.QueueUserWorkItem(Test,"种子3号");
            ThreadPool.QueueUserWorkItem(Test,"种子4号");
            ThreadPool.QueueUserWorkItem(Test,"种子5号");
            Console.ReadKey();
        }

输出结果:

任务

开启任务的两种种方式:

(1)Task t = new Task(ThreadMethod);//传递一个需要线程去执行的方法

         t.Start();//任务开启

(2)TaskFactory tf = new TasFactory();//创建一个任务的工厂

         Task t = tf.StartNew(ThreadMethod); //通过StartNew方法开启

连续任务

Task t1=new Task(DoFirst);

Task t2=t1.ContinueWith(DoScend);

Task t3=t1.ContinueWith(DoScend);

Task t4=t2.ContinueWith(DoScend);

我们在一个任务中启动一个新的任务,相当于新的任务是当前任务的子任务,两个任务异步执行,如果父任务执行完了但是子任务没有执行完,它的状态会设置为WaitingForChildrenToComplete,只有子任务也执行完了,父任务的状态就变成RunToCompletion。

例如,执行时t2比t4先执行完毕,则会进入等待状态,只有等t4执行完毕才会变成完成状态。

线程争用问题

class MyThread
    {
        public int state = 5;
        public void ChangeState()
        {
            state++;
            if (state == 5)
            {
                Console.WriteLine("state=5");
            }
            state = 5;
        }
    }
.......
        static void ChangeState(object o)
        {
            MyThread myThread = o as MyThread;
            while (true)
            {
                myThread.ChangeState();
            }
        }
        static void Main(string[] args)
        {
            MyThread m = new MyThread();
            Thread t1 = new Thread(ChangeState);//创建一个线程
            Thread t2 = new Thread(ChangeState);//创建另一个线程
            t1.Start(m);
            t2.Start(m);
        }

当我们运行Main时

结果就会出现这段本不应该出现的语句

这是由于两个线程同时抢占了同一个MyThread对象也就是m的内存地址,导致一个线程中执行到state赋值为5的语句时,另一个线程中正好下一句在判断state是否等于5,这时就会出现线程争用的问题。

解决这个问题就需要给线程上锁

给上面的代码添加一个锁

while (true)
            {
                lock (myThread)//向系统申请获取并锁定该对象,如果该对象没有被锁定,则可以获取并执行后面的语句
                    //如果已经锁定,则这个语句会暂停,直到申请到该对象
                {
                    myThread.ChangeState();
                }
            }

要注意,lock语句只能锁定引用类型

线程死锁

public void  Method1(){
            while(true){
                    lock(a1){
                         lock(a2){
                                .......
                                      }
                                }
                            }
}
public void  Method2(){
            while(true){
                    lock(a2){
                         lock(a1){
                                .......
                                      }
                                }
                            }
}

此时,用两个线程分别调用这两个方法时,会出现死锁,当Method1锁定a1时,Method2锁定了a2,两个语句继续往下走,则都会暂停,因为下面要获取的对象都被锁定了。

要解决死锁问题,只有在编程开始设计阶段,就要确定好锁定的顺序。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值