1. 程式人生 > >C#高階篇(6)

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.檢測委託執行緒的結束-通過控制代碼和回撥函式

通過控制代碼檢測執行緒結束

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(); 
                }
            }
        }

死鎖

兩個執行緒都呼叫了兩個屬性。但是他們對屬性的鎖的先後順序不一樣,這樣一個執行緒鎖一個屬性。兩個執行緒都申請不到另一個屬性。

形成了死鎖。

在程式設計的時候設計好鎖定的順序。避免出現死鎖的情況。