1. 程式人生 > >C#程式設計 執行緒,任務和同步(2) 開啟執行緒

C#程式設計 執行緒,任務和同步(2) 開啟執行緒

建立執行緒的幾種方法:

1 非同步委託

建立執行緒的一種簡單方式是定義一個委託,並非同步呼叫它。 委託是方法的型別安全的引用。Delegate類 還支援非同步地呼叫方法。在後臺,Delegate類會建立一個執行任務的執行緒。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace _016_執行緒_委託方式發起執行緒
{
    class Program
    {
        //一般我們會為比較耗時的操作 開啟單獨的執行緒去執行,比如下載操作
        static int Test(int i, string str)
        {
            Console.WriteLine("test:" + i + str);
            Thread.Sleep(100);//讓當親執行緒休眠(暫停執行緒的執行) 單位ms
            return 100;
        }
        static void Main(string[] args)
        {//在main執行緒中執行 一個執行緒裡面語句的執行 是從上到下的
            // 通過委託 開啟一個執行緒
            Func<int, string, int> a = Test;
            // 開啟一個新的執行緒去執行 a所引用的方法 
            IAsyncResult ar = a.BeginInvoke(100, "   mytest", null, null);
            // IAsyncResult 可以取得當前執行緒的狀態
            Console.WriteLine("main");
            while (ar.IsCompleted == false)//如果當前執行緒沒有執行完畢
            {
                Console.Write(".");
                Thread.Sleep(10); //控制子執行緒的檢測頻率
            }
            int res = a.EndInvoke(ar);//取得非同步執行緒的返回值
            Console.WriteLine(res);
        }
    }
}

輸出結果:

 

上面是通過迴圈檢測判斷執行緒是否結束。當我們通過BeginInvoke開啟一個非同步委託的時候,返回的結果是IAsyncResult,我們可以通過它的AsyncWaitHandle屬性訪問等待控制代碼。這個屬性返回一個WaitHandler型別的物件,它中的WaitOne()方法可以等待委託執行緒完成其任務,WaitOne方法可以設定一個超時時間作為引數(要等待的最長時間),如果發生超時就返回false。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace _016_執行緒_委託方式發起執行緒
{
    class Program
    {
        //一般我們會為比較耗時的操作 開啟單獨的執行緒去執行,比如下載操作
        static int Test(int i, string str)
        {
            Console.WriteLine("test:" + i + str);
            Thread.Sleep(100);//讓當親執行緒休眠(暫停執行緒的執行) 單位ms
            return 100;
        }
        static void Main(string[] args)
        {//在main執行緒中執行 一個執行緒裡面語句的執行 是從上到下的
            // 通過委託 開啟一個執行緒
            Func<int, string, int> a = Test;
            IAsyncResult ar = a.BeginInvoke(100, "   mytest", null, null);// 開啟一個新的執行緒去執行 a所引用的方法 
            // IAsyncResult 可以取得當前執行緒的狀態
            Console.WriteLine("main");
            //檢測執行緒結束
            bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);//1000毫秒錶示超時時間,如果等待了1000毫秒 執行緒還沒有結束的話 那麼這個方法會返回false 如果在1000毫秒以內執行緒結束了,那麼這個方法會返回true
            if (isEnd)
            {
                int res = a.EndInvoke(ar);
                Console.WriteLine(res);
            }
        }
    }
}

等待委託的結果的第3種方式是使用非同步回撥。在BeginInvoke的第三個引數中,可以傳遞一個滿足AsyncCallback委託的方法,AsyncCallback委託定義了一個IAsyncResult型別的引數其返回型別是void。對於最後一個引數,可以傳遞任意物件,以便從回撥方法中訪問它。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace _016_執行緒_委託方式發起執行緒
{
    class Program
    {
        //一般我們會為比較耗時的操作 開啟單獨的執行緒去執行,比如下載操作
        static int Test(int i, string str)
        {
            Console.WriteLine("test:" + i + str);
            Thread.Sleep(100);//讓當親執行緒休眠(暫停執行緒的執行) 單位ms
            return 100;
        }

        static void Main(string[] args)
        {//在main執行緒中執行 一個執行緒裡面語句的執行 是從上到下的
            Console.WriteLine("main");
            //通過回撥 檢測執行緒結束
            Func<int, string, int> a = Test;
            // 倒數第二個引數是一個委託型別的引數,表示回撥函式,就是當執行緒結束的時候會呼叫這個委託指向的方法 倒數第一個引數用來給回撥函式傳遞資料
            IAsyncResult ar = a.BeginInvoke(100, "   mytest", OnCallBack, a);
            Console.ReadKey();
        }

            static void OnCallBack( IAsyncResult ar )
            {
                Func<int, string, int> a = ar.AsyncState as Func<int, string, int>;
                int res =  a.EndInvoke(ar);
                Console.WriteLine(res+"在回撥函式中取得結果");
            }

    }
}

 在第三種方法中,我們可以使用Lamba表示式

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace _016_執行緒_委託方式發起執行緒
{
    class Program
    {
        //一般我們會為比較耗時的操作 開啟單獨的執行緒去執行,比如下載操作
        static int Test(int i, string str)
        {
            Console.WriteLine("test:" + i + str);
            Thread.Sleep(100);//讓當親執行緒休眠(暫停執行緒的執行) 單位ms
            return 100;
        }
        static void Main(string[] args)
        {
            Console.WriteLine("main");

            //通過回撥 檢測執行緒結束
            Func<int, string, int> a = Test;
            a.BeginInvoke(100, "siki", ar =>
            {
                int res = a.EndInvoke(ar);
                Console.WriteLine(res + "在lambda表示式中取得");
            }, null);

            Console.ReadKey();
        }
    }
}

 

2 通過Thread類

 我們可以通過Thread類來建立執行緒,我們構造一個thread物件的時候,可以傳遞一個靜態方法,也可以傳遞一個物件的普通方法。我們先傳遞一個靜態方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace _017_執行緒_通過Thread發起執行緒 {
    class Program {
        // 注意:執行緒呼叫的方法定義中,引數要用object
        static void DownloadFile(object filename)
        {
            // Thread.CurrentThread.ManagedThreadId表示當前執行緒ID
            Console.WriteLine("開始下載:"  +Thread.CurrentThread.ManagedThreadId +filename);
            Thread.Sleep(2000);
            Console.WriteLine("下載完成");

        }
        static void Main(string[] args) {
            //建立執行緒,傳入要執行的方法
            Thread t = new Thread(DownloadFile);
            // 開啟執行緒,如果執行緒呼叫的方法有引數,在Start中傳入
            t.Start("test");
            Console.WriteLine("Main");
            Console.ReadKey();

            // 使用Lamba表示式方法
            //Thread t = new Thread(() =>
            //{
            //    Console.WriteLine("開始下載:" + Thread.CurrentThread.ManagedThreadId);
            //    Thread.Sleep(2000);
            //    Console.WriteLine("下載完成");
            //});
            //t.Start();
        }
    }
}

輸出結果:

 

下面我們傳遞一個物件的普通方法,先定義一個類:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace _017_執行緒_通過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("下載完成");
        }

    }
}

然後建立執行緒:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace _017_執行緒_通過Thread發起執行緒 {
    class Program {
        static void Main(string[] args) {

            MyThread my = new MyThread("xxx.bt","http://www.xxx.bbs");
            Thread t = new Thread(my.DownFile);
            t.Start();
        }
    }
}

 

3 使用執行緒池

建立執行緒需要時間。 如果有不同的小任務要完成,就可以事先建立許多執行緒 , 在應完成這些任務時發出請求。 這個執行緒數最好在需要更多的執行緒時增加,在需要釋放資源時減少。

不需要 自己建立執行緒池,系統已經有一個ThreadPool類管理執行緒。 這個類會在需要時增減池中執行緒的執行緒數,直到達到最大的執行緒數。 池中的最大執行緒數是可配置的。 在雙核 CPU中 ,預設設定為1023個工作執行緒和 1000個 I/o執行緒。也可以指定在建立執行緒池時應立即啟動的最小執行緒數,以及執行緒池中可用的最大執行緒數。 如果有更多的作業要處理,執行緒池中執行緒的個數也到了極限,最新的作業就要排隊,且必須等待執行緒完成其任務。

使用執行緒池需要注意的事項:     

     執行緒池中的所有執行緒都是後臺執行緒 。 如果程序的所有前臺執行緒都結束了,所有的後臺執行緒就會停止。 不能把入池的執行緒改為前臺執行緒 。     

    不能給入池的執行緒設定優先順序或名稱。     

    入池的執行緒只能用於時間較短的任務。 如果執行緒要一直執行(如 Word的拼寫檢查器執行緒),就應使用Thread類建立一個執行緒。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace _018_執行緒_執行緒池 {
    class Program {
        // 注意:使用執行緒池新增的執行函式,必須要有一個object型別的引數,即時不用
        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);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            Console.ReadKey();

        }
    }
}

輸出結果:

 

4 通過任務建立

在.NET4 新的名稱空間System.Threading.Tasks包含了類抽象出了執行緒功能,在後臺使用的ThreadPool進行管理的。任務表示應完成某個單元的工作。這個工作可以在單獨的執行緒中執行,也可以以同步方式啟動一個任務。     任務也是非同步程式設計中的一種實現方式。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace _019_執行緒_任務 {
    class Program {
        static void ThreadMethod() {
            Console.WriteLine("任務開始");
            Thread.Sleep(2000);
            Console.WriteLine("任務結束");
        }
        static void Main(string[] args) {
            // 通過Task建立
            Task t = new Task(ThreadMethod);
            t.Start();

            // 通過任務工廠建立
            //TaskFactory tf = new TaskFactory();
            //Task t = tf.StartNew(ThreadMethod);

            Console.WriteLine("Main");
            Console.ReadKey();
        }
    }
}

輸出結果:

 

連續任務:如果一個任務t1的執行是依賴於另一個任務t2的,那麼就需要在這個任務t2執行完畢後才開始執行t1。這個時候我們可以使用連續任務。

static void DoFirst(){
	Console.WriteLine("do  in task : "+Task.CurrentId);
	Thread.Sleep(3000);
}
static void DoSecond(Task t){
	Console.WriteLine("task "+t.Id+" finished.");
	Console.WriteLine("this task id is "+Task.CurrentId);
	Thread.Sleep(3000);
}
Task t1 = new Task(DoFirst);
Task t2 = t1.ContinueWith(DoSecond);
Task t3 = t1.ContinueWith(DoSecond);
Task t4 = t2.ContinueWith(DoSecond);

 任務層次結構:我們在一個任務中啟動一個新的任務,相當於新的任務是當前任務的子任務,兩個任務非同步執行,如果父任務執行完了但是子任務沒有執行完,它的狀態會設定為WaitingForChildrenToComplete,只有子任務也執行完了,父任務的狀態就變成RunToCompletion

static void Main(){
	var parent = new Task(ParentTask);
	parent.Start();
	Thread.Sleep(2000);
	Console.WriteLine(parent.Status);
	Thread.Sleep(4000);
	Console.WriteLine(parent.Status);
	Console.ReadKey();
}
static void ParentTask(){
	Console.WriteLine("task id "+Task.CurrentId);
	var child = new Task(ChildTask);
	child.Start();
	Thread.Sleep(1000);
	Console.WriteLine("parent started child , parent end");
}
static void ChildTask(){
	Console.WriteLine("child");
	Thread.Sleep(5000);
	Console.WriteLine("child finished ");