C#高级篇(6)

1.进程和线程
一般情况下,一个应用程序下有一个进程,有好几个线程

在一个进程中有多个线程,这些线程共享进程的内存空间。

在进程中通过互斥锁,防止多个线程同一时间读写某一块内存区域。

使用信号量保证多个线程不会相互冲突

通过委托开启一个线程,一般一个比较耗时的操作,开启单独的线程去执行,比如下载

        static void Test()
        {
            Console.WriteLine("test");
        }
        static void Main(string[] args)
        {
            Action a = Test;
            a.BeginInvoke(null,null);
            Console.WriteLine("main");
            Console.ReadKey();
        }

这里委托开启的线程和Main是异步执行的

传递一个参数

        static void Test(int i)
        {
            Console.WriteLine(i+" test");
        }
        static void Main(string[] args)
        {
            Action<int> a = Test;
            a.BeginInvoke(100,null,null);
            Console.WriteLine("main");
            Console.ReadKey();
        }

IAsyncResult可以取得当前线程的状态

        static int Test(int i)
        {
            Console.WriteLine(i+" test");
            Thread.Sleep(10);//让当前线程休眠 单位ms
            return 66;
        }
        static void Main(string[] args)
        {
            Func<int,int> a = Test;
            IAsyncResult ar =  a.BeginInvoke(100,null,null);
            Console.WriteLine("main");
            while(ar.IsCompleted == false)
            {
                Console.Write(".");
            }
            int res = a.EndInvoke(ar);
            Console.WriteLine(res);
            Console.ReadKey();
        }

通过BeginInvoke方法开启一个线程,通过EndInvoke结束线程,线程结束方法才能有返回值

2.检测委托线程的结束-通过句柄和回调函数

通过句柄检测线程结束

扫描二维码关注公众号,回复: 4385891 查看本文章

ar获得一个AsyncWaitHandle等待的句柄,调用WaitOne方法,等待1000毫秒,超时返回false。

bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);//1000表示超时时间,如果等待了1000线程还没有结束,返回false。
            if(isEnd)
            {
                int res = a.EndInvoke(ar);
                Console.WriteLine(res);
            }

通过回调函数检测线程结束

将回调函数作为BeginInvoke参数传递,最后一个参数为给回调函数传递数据的

在回调函数中通过ar.AsyncState获取传过来的数据(传入的委托a)

static void Main(string[] args)
        {            //通过回调函数检测线程结束
            Func<int, int> a = Test;
            IAsyncResult ar =  a.BeginInvoke(100,OnCallBack,a);

            Console.ReadKey();
        }
        static void OnCallBack(IAsyncResult ar)
        {
            Console.WriteLine("子线程END");
            Func<int, int> a = ar.AsyncState as Func<int, int>;
            int res = a.EndInvoke(ar);
            Console.WriteLine(res + "在回调函数中取得结果");
        }
            Func<int, int> a = Test;
            //IAsyncResult ar =  a.BeginInvoke(100,OnCallBack,a);
            a.BeginInvoke(100, ar =>
             {
                 int res = a.EndInvoke(ar);
                 Console.WriteLine(res + "lambda表达式");
             },null);

通过Lambda表达式来结束

3.线程开启:通过Thread类

class Program
    {
        static void DownloadFile()
        {
            Console.WriteLine("开始下载"+Thread.CurrentThread.ManagedThreadId);//获取线程ID
            Thread.Sleep(2000);
            Console.WriteLine("END");
        }
        static void Main(string[] args)
        {
            //创建出来Thread,这个线程并没有启动
            Thread t = new Thread(DownloadFile);
            t.Start();
            Console.WriteLine("Main");
            Console.ReadKey();
        }
    }

通过Lambda表达式开启线程

Thread t = new Thread(() =>
            {
                Console.WriteLine("开始下载 " + Thread.CurrentThread.ManagedThreadId);//获取线程ID
                Thread.Sleep(2000);
                Console.WriteLine("END");
            });
            t.Start();

通过创建对象来创建线程

先定义一个类

再创建一个这个类的对象,再构造一个thread对象的时候,可以传递一个静态方法也可以传递一个普通的方法。

class MyThread
    {
        private string filename;
        private string filepath;
        public MyThread(string fileName,string filePath)
        {
            this.filename = fileName;
            this.filepath = filePath;
        }
        public void DownFile()
        {
            Console.WriteLine("开始下载" + filepath + filename);
            Thread.Sleep(2000);
            Console.WriteLine("End");
        }
    }
MyThread my = new MyThread("xxx.bt", "www.baidu.com");
            Thread t = new Thread(my.DownFile);
            t.Start();

4.线程中的后台线程和前台线程

只要存在没有结束的前台线程,应用程序的进程就没有结束

默认情况下Thread类创建的线程是前台线程,线程池中的线程是后台线程

如果所有前台线程已经结束,那么后台线程会被终止

通过线程的IsBackground方法,将线程设置为后台线程

            Thread t = new Thread(DownloadFile);
            t.IsBackground = true;
            t.Start("Loading");

线程的优先级

线程会根据优先级来执行

控制线程

Running或者Unstarted。再调用了Start方法后,都能带操作系统的线程调度器选择要运行得线程,

这个线程才会冲Unstarted修改为Running。使用Thread.Sleep()方法可以让当前线程休眠进入WaitSleepJoin状态

Abort方法会停止线程,这个方法会在要终止得线程抛出一个ThreadAbortException得异常,我们可以通过try catch来处理这个以常,

然后再线程结束进行一些清理工作。

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

5.线程池

Thread Pool一个线程池中有事先创建好的线程。使用线程池做一些小任务,创建出来的线程默认是后台线程。

线程池开启线程得方法要传递一个参数

class Program
    {
        static void ThreadMethod(object state)
        {
            Console.WriteLine("线程开始"+Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(2000);
            Console.WriteLine("线程结束");
        }
        static void Main(string[] args)
        {
            //开启一个工作线程
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            Console.ReadKey();
        }
    }

6.任务

给任务传递一个方法让他执行

任务开启一个线程传递的方法不能带有参数

class Program
    {
        static void ThreadMethod()
        {
            Console.WriteLine("任务开始");
            Thread.Sleep(2000);
            Console.WriteLine("任务结束");
        }
        static void Main(string[] args)
        {
            Task t = new Task(ThreadMethod);
            t.Start();
            Console.WriteLine("Main");
            Console.ReadKey();
        }
    }

或者通过TaskFactory工厂模式开启一个任务,返回一个Task对象。

            TaskFactory tf = new TaskFactory();
            Task t = tf.StartNew(ThreadMethod);

连续任务

如果任务t1的执行是依赖于另一个任务t2的,那么就需要再这个任务t2执行完毕后才开始执行t1.这个时候我们可以使用连续任务

任务层次结构

我们在一个任务中启动另一个新的任务,相当于新的任务是当前任务的子任务,两个任务异步执行,如果父任务执行完了但是子任务没有执行完

他的状态会设置为WaitingForChildrenToComplete,只有子任务也执行完了,父任务的状态就变成RunToCompletion

7.线程问题-争用条件和死锁

eg:

创建一个类,定义一个方法,先将state++,再判断当state == 5 的时候打印state =5.

class MyThreadObject
    {
        private int state = 5;
        public void ChangeState()
        {
            state++;
            if(state == 5)
            {
                Console.WriteLine("state =5");
            }
            state = 5;
        }
    }

在main函数中开启两个线程,这两个线程都是循环调用MyThreadObject中的方法。

class Program
    {
        static void ChangeState(object o)
        {
            MyThreadObject m = o as MyThreadObject;
            while (true)
            {
                m.ChangeState();
            }
        }
        static void Main(string[] args)
        {
            MyThreadObject m = new MyThreadObject();
            Thread t = new Thread(ChangeState);
            t.Start(m);
            Thread d = new Thread(ChangeState);
            d.Start(m);

            Console.ReadKey();
        }
    }

结果输出了很多state = 5

两个线程修改同一个属性,但是两个线程不同步,这样这个属性的值,就不能确定了。

解决这个问题要给当前对象加一个锁

修改代码

使用Lock锁定m,这样如果使用m,就会锁定m,如果其他线程也调用m,就会等待m解除锁定才能使用m。

static void ChangeState(object o)
        {
            MyThreadObject m = o as MyThreadObject;
            while (true)
            {
                lock (m)
                {
                    m.ChangeState(); 
                }
            }
        }

死锁

两个线程都调用了两个属性。但是他们对属性的锁的先后顺序不一样,这样一个线程锁一个属性。两个线程都申请不到另一个属性。

形成了死锁。

在编程的时候设计好锁定的顺序。避免出现死锁的情况。

猜你喜欢

转载自blog.csdn.net/Game_Builder/article/details/81225527