1. 程式人生 > >任運自在:執行緒(Thread)與委託(Invoke和BeginInvoke)和封裝

任運自在:執行緒(Thread)與委託(Invoke和BeginInvoke)和封裝

執行緒(Thread)與委託(Invoke和BeginInvoke)
這幾天專門玩執行緒與委託,到處查詢資料看,漸漸明白了用法、寫法和一些注意事項;

描述:
什麼是程序呢?當一個程式開始執行時,它就是一個程序,程序所指包括執行中的程式和程式所使用到的記憶體和系統資源。而一個程序又是由多個執行緒所組成的,執行緒是程式中的一個執行流,每個執行緒都有自己的專有暫存器(棧指標、程式計數器等),但程式碼區是共享的,即不同的執行緒可以執行同樣的函式。

Control.Invoke 方法 (Delegate) :在擁有此控制元件的基礎視窗控制代碼的執行緒上執行指定的委託。
Control.BeginInvoke 方法 (Delegate) :在建立控制元件的基礎控制代碼所線上程上非同步執行指定委託。
Control的Invoke和BeginInvoke的引數為delegate,委託的方法是在Control的執行緒上執行的,也就是我們平時所說的UI執行緒。

何時採用簡單歸納:
1、提高CPU的利用率,從而提高了程式的效率;
2、當程式執行會卡住軟體介面,為了使人不用焦慮等待時,採取執行緒與委託來處理,從而使軟體介面執行流暢;
3、處理大量資料時間較長(顯示一個進度條給介面)或不需要馬上得到結果反饋給軟體介面時;

注意事項:執行緒是各自獨立進行管理的,一個執行緒不能包含另一個執行緒;即:用Thread建立的執行緒是一個執行緒,Control是一個執行緒,這是兩個獨立的執行緒;委託則專屬於Control執行緒;至少目前我是這樣理解的,因此,初涉這個領域時不小心就會犯執行緒相互交涉而發生錯誤;更具體檢視微軟或網路上相關資料就不贅述了。

舉例是最生動的說明,看下面例子:

要執行的操作是計算一個文字控制元件中有多少個字元,包含多少回車數量:

private delegate void 數字委託(int 數字);

private void 計算字數(int 回車)

{

  顯示控制元件.Text = "字數:" + 文字.TextLength.ToString() + ";";

  顯示控制元件.Text += "其中包含" + 回車.ToString() + "回車數";

}

//請注意上面方法包含Control執行緒,而下面方法不包含Control執行緒,並註釋掉公共類傳遞引數,可自己除錯。
private void 計算回車數量(object 資料)

{

  int 回車 = 0;
            for (int i = 0, 數量 = 資料.ToString().Length; i < 數量; i++)
            {
                if (資料.ToString().Contains("\n")) 回車++;
                if (資料.ToString().IndexOf("\n") + 1 < 資料.ToString().Length)
                    資料 = 資料.ToString().Substring(資料.ToString().IndexOf("\n") + 1);
                else break;
                if (!資料.ToString().Contains("\n")) break;
            }
           this.BeginInvoke(new 數字委託(計算字數), 回車);
}

private void 按鈕_Click(object sender, EventArgs e)

{
  new Thread(new ParameterizedThreadStart(計算回車數量)).Start(文字控制元件.Text);
}
//宣告並執行建立的執行緒,同時傳遞引數,Start傳遞的是object型別
執行正常。

在private void 計算回車數量(object 資料)方法中用委託來返回計算結果,引數為delegate,在最上一行宣告,這樣就返回到Control執行緒。
假如不用委託而直接用:計算字數(回車);將提示錯誤,原因就是不同執行緒發生交涉。如下:
private void 計算回車數量(object 資料)

{

  int 回車 = 0;
            for (int i = 0, 數量 = 資料.ToString().Length; i < 數量; i++)
            {
                if (資料.ToString().Contains("\n")) 回車++;
                if (資料.ToString().IndexOf("\n") + 1 < 資料.ToString().Length)
                    資料 = 資料.ToString().Substring(資料.ToString().IndexOf("\n") + 1);
                else break;
                if (!資料.ToString().Contains("\n")) break;
            }     計算字數(回車);
}

宣告並執行執行緒語句不同寫法:
--------------------------------------------------用公共類傳遞
public class 共類 { public string 資料 { get; set; } public int 數值 { get; set; } }
共類 文字 = new 共類(); 文字.資料 = 文字控制元件.Text;
Thread 計算 = new Thread(new ParameterizedThreadStart(計算回車數量)); 計算.Start(文字); 計算.Abort();
-----------------------------------------------------------------------------------------------------
string 文字 = 文字控制元件.Text; new Thread(delegate() { 計算回車數量(文字); }).Start();

Thread 執行緒 = new Thread(delegate() { 執行緒另存檔案(); });
執行緒.SetApartmentState(System.Threading.ApartmentState.STA);//.MST
執行緒.Start();
------------------------------------------------------------------------------------

初涉的人為如何返回執行緒結果苦惱,採用很多種方法,我這裡採用委託直接返回執行緒結果;

下面看看委託:一般我是這樣寫委託就可以了,this.BeginInvoke(new 數字委託(計算字數), 回車);

/*還看到下面的一種寫法是在委託結束後回撥結果的:
//此處開始非同步執行,並且可以給出一個回撥函式
計算.BeginInvoke(文字控制元件.Text, new AsyncCallback(委託回撥), null);

delegate int 申明委託簽名(string 傳入值);

申明委託簽名 計算 = new 申明委託簽名(委託執行);//把委託和具體的方法關聯起來


public static int 委託執行(string 文字)//委託呼叫的方法
{

  int 回車 = 0;

 for (int i = 0, 數量 = 資料.ToString().Length; i < 數量; i++)
            {
                if (資料.ToString().Contains("\n")) 回車++;
                if (資料.ToString().IndexOf("\n") + 1 < 資料.ToString().Length)
                    資料 = 資料.ToString().Substring(資料.ToString().IndexOf("\n") + 1);
                else break;
                if (!資料.ToString().Contains("\n")) break;
            }     return 回車;

}


public void 委託回撥(IAsyncResult 返回值)

{
   this.BeginInvoke(new 數字委託(計算字數), 計算.EndInvoke(返回值));
}
*/

同樣我採用委託返回結果,如果直接用:計算字數(計算.EndInvoke(返回值));將提示錯誤。

使用 AsyncCallback 委託在一個單獨的執行緒中處理非同步操作的結果。AsyncCallback 委託表示在非同步操作完成時呼叫的回撥方法。回撥方法採用 IAsyncResult 引數,該引數隨後可用來獲取非同步操作的結果。

委託注意事項:委託的方法傳入引數必須對應,否則發生錯誤;
如:委託方法的傳入引數是string則宣告也必須是:private delegate void 文字委託(string 內容);

委託引數的數量必須與委託方法引數數量相等且型別必須一致;
如:委託方法的傳入引數是:DateTime 日期, DateTime 預測日期, string 內容,則宣告也必須對應:
private delegate void 委託(DateTime 日期, DateTime 預測日期, string 內容);

這裡順便提及是因為看到有些提問是否可以帶幾個引數;還有執行緒如何傳參的,有人回覆設一個公共變數來傳參,提問人覺得很遺憾,各人方法不盡相同,也屬正常,無可非議。

同時還應該注意:執行緒與非同步委託完成時間是不定的,設計時也應該慎重考慮或用除錯決定。

以上就是這些天專門玩執行緒與委託的一些經驗,今天憑著思路就寫了這些,知道寫得不好,看了莫笑。

初學靈活變通和試驗除錯相對比較弱,這裡再給一個直接呼叫多引數方法例子:
Thread 執行緒 = new Thread(delegate() { this.Invoke(new Action(() => 載入快捷選單(快捷選單, 快捷引數, 快捷事件))); });
執行緒.Start();

其實執行緒沒那麼難搞定,這裡給個定式:
Thread 執行緒 = new Thread(delegate()
 {
     this.Invoke(new Action(() => {/*如果涉及UI執行緒原始碼放這裡,如果沒有刪除這句*/}));
 });
執行緒.Start();

new Thread(delegate() { this.Invoke(new Action(delegate() { 乾坤大挪移(快捷選單, 乾坤大挪移引數); })); }).Start();

使用匿名委託:
this.Invoke(new Action(delegate() { /*任何語句或方法*/}));
new Thread(delegate() { /*不涉及UI執行緒任何語句或方法*/ }).Start();

Thread 執行緒 = new Thread(delegate()
{/*原始碼放這裡就可以了*/}); 執行緒.Start();執行緒.Join();/*後續其他程式碼*/

有時方法外使用執行緒可以改為方法內使用執行緒(多引數傳遞)是一樣的,下面是改動的例子:
private void 乾坤大挪移(ContextMenuStrip 選單名, string[] 子引數)
 {
      Thread 執行緒 = new Thread(delegate()
     {
        this.Invoke(new Action(() =>
        {
           /*原始碼放這裡就可以了*/
        }));
     }); 執行緒.Start();
 }

其實執行緒和委託使用起來是很方便的,特別是跨執行緒訪問也很簡單,只要是涉及到控制元件執行緒就使用委託就可以了,下面是改動的例子:
private void 時間_Tick(object sender, EventArgs e)
 {
      Thread 執行緒 = new Thread(delegate()
      {
          if (秒 < 59) 秒++; else { 秒 = 0; 分++; } if (分 == 60) { 分 = 0; 時++; } if (時 == 5) 時 = 0;
          this.Invoke(new Action(() =>
          顯示時間.Text = DateTime.Parse(時.ToString("0:") + 分.ToString("00:") + 秒.ToString("00")).ToLongTimeString()));
       }); 執行緒.Start();
 }
當使用非同步委託(BeginInvoke)需注意,有可能造成介面反應更忙,一般不與介面反應有關不輕易使用。

        private void button1_Click(object sender, EventArgs e)
        {

            IAsyncResult 呼叫返回 = listBox1.BeginInvoke(new Action(() =>
            {
                Thread.Sleep(2000);
                listBox1.Items.Add(Thread.CurrentThread.Name);
            }));
            listBox1.EndInvoke(呼叫返回);

            listBox1.Invoke((EventHandler)delegate
            {
                Thread.Sleep(2000);
                listBox1.Items.Add(Thread.CurrentThread.Name);//執行到這裡,其實把上一個函式的睡著的非同步給弄醒了
            });
            listBox1.BeginInvoke(new MethodInvoker(delegate
            {
                Thread.Sleep(2000);//呼叫的時候需要等待的時間,非同步是指CPU自己有空閒的時候合理分配空間給予執行;跟此時間無關;
                listBox1.Items.Add(Thread.CurrentThread.Name);
            }));

        }補充參考

Task 類

var t = Task.Factory.StartNew(() => button1_Click(null ,null ));

執行緒池

            System.Threading.ThreadPool.QueueUserWorkItem(
                new System.Threading.WaitCallback(一些長期任務));

            System.Threading.ThreadPool.QueueUserWorkItem(
                new System.Threading.WaitCallback(另一個長期任務),傳遞);

        private void 一些長期任務(Object state)
        {
            // 插入程式碼來執行一項艱鉅的任務。

            this.Invoke(new Action(() => { resultLabel.Text = "0"; }));

            int aa = 100;
            do
            {
                System.Threading.Thread.Sleep(1000);
                this.Invoke(new Action(() => { resultLabel.Text = (int.Parse(resultLabel.Text) + 1).ToString(); }));
            } while (--aa > 0);
        }

        private void 另一個長期任務(Object 引數)
        {
            // 插入程式碼來執行一項艱鉅的任務,引數包裝

           string aa=引數.ToString();
        }

-----------------------------------------------------------------------------------

 有關封裝與三目運算子應用:

Func<stringbool> 邏輯 = delegate(string 年資訊)

{

if (年資訊.Contains("11")) return false;

return true;/*在這裡可以寫多語句處理,寫在呼叫之前*/ 

};

            Func<string, string[], bool> 查對 = (string 資料, string[] 隔符) =>
            {
                return 隔符.Select(元素 => 資料.Contains(元素)).Any(比 => 比 == true);
            };

return (年資訊.Contains("00")) ? true : 邏輯(年資訊);

上面利用有參有返回(僅1個引數和返回值)

委託有引數無返回:

            Action<DateTime[]> 日期計算 = delegate(DateTime[] 日期)
            {
                /*在這寫處理程式碼*/
            };

呼叫:日期計算(new DateTime[] { 日期1, 日期2 });

委託無參有返回:

            Func<string[]> 處理 = delegate
            {
                string[] 內容 = new string[0];
                /*在這寫處理程式碼*/
                return 內容;
            };

Action 顯示 = delegate()

{

升起提示窗體(顯示事件.Text.Replace("\r\n""")); 

};

Action 顯示 = ()=>
{

};

Action<int> 顯示 = (引數)=>
{

};

顯示(/*在需要呼叫的地方寫這行程式碼*/);

Parallel.Invoke(new System.Action(delegate() {/*包含不適合於"雷姆達表示式的"程式碼塊*/}));

Parallel.Invoke(delegate()
            {
                this.BeginInvoke(new Action(delegate()
                { /*適用巢狀迴圈提高速度*/}));
             });

 下面轉來自微軟程式碼例子:

            Action<object> action = (object obj) =>
            {
                Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);
            };
            Task t1 = new Task(action, "alpha");
            Task t2 = Task.Factory.StartNew(action, "beta");
            t2.Wait();
            t1.Start();

            Console.WriteLine("t1 has been launched. (Main Thread={0})", Thread.CurrentThread.ManagedThreadId);
            t1.Wait();
            Task t3 = new Task(action, "gamma");
            t3.RunSynchronously();
            t3.Wait();



        public Form1()
        {
            InitializeComponent();
            backgroundWorker1.WorkerReportsProgress = true;
            backgroundWorker1.WorkerSupportsCancellation = true;
        }

        private void startAsyncButton_Click(object sender, EventArgs e)
        {
            if (backgroundWorker1.IsBusy != true)
            {
                // 啟動非同步操作。
                backgroundWorker1.RunWorkerAsync();
            }
        }

        private void cancelAsyncButton_Click(object sender, EventArgs e)
        {
            if (backgroundWorker1.WorkerSupportsCancellation == true)
            {
                // 取消非同步操作。
                backgroundWorker1.CancelAsync();
            }
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            for (int i = 1; i <= 10; i++)
            {
                if (worker.CancellationPending == true)
                {
                    e.Cancel = true;
                    break;
                }
                else
                {
                    // 執行耗時的操作,並報告進度。
                    System.Threading.Thread.Sleep(500);
                    worker.ReportProgress(i * 10);
                }
            }
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            resultLabel.Text = (e.ProgressPercentage.ToString() + "%");
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled == true)
            {
                resultLabel.Text = "取消!";
            }
            else if (e.Error != null)
            {
                resultLabel.Text = "錯誤: " + e.Error.Message;
            }
            else
            {
                resultLabel.Text = "做!";
            }
        }







    Public Sub 定時事件(ByVal state As Object)
        Me.BeginInvoke(
            New Action(
                       Sub()
                           移動字幕.Left = 移動字幕.Left - 1
                           If 移動字幕.Right < 0 Then 移動字幕.Left = Me.Width
                       End Sub)
                   )
    End Sub



        void 控制元件非同步處理(Action 無返回方法)
        {
            Parallel.Invoke(delegate()
            {
                this.BeginInvoke(new Action(delegate()
                {
                    無返回方法();
                }));
            }); 
        }

        void 背景(DataTable 資料表)
        {
            迴圈 = 資料表.Rows.Count - 1; int 列 = 資料表.Columns.Count - 1;
            if (迴圈 > 0) while (true)
                {
                    資料列表.Rows[迴圈].Cells[列].Style.BackColor = Color.Transparent;
                    if (--列 < 0) { if (--迴圈 < 0) break; 列 = 資料表.Columns.Count - 1; }
            }
        }
        void 顯示(DataTable 資料表)
        {
            資料列表.DataSource = null;
            資料列表.DataSource = 資料表;
            資料列表.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
        }

            if (!new System.IO.FileInfo(@System.Environment.CurrentDirectory + "\\中草藥資料表.XML").Exists)
                動態表格(new string[] { "名稱", "別名", "性味", "歸經", "主治" });
            else
            {
                資料表載入(中草藥資料表);
                控制元件非同步處理(() => 顯示(中草藥資料表));
                控制元件非同步處理(() => 背景(中草藥資料表));
                this.Text += "     已載入:" + 中草藥資料表.Rows.Count + "條資料。";
            }
            if (new System.IO.FileInfo(@System.Environment.CurrentDirectory + "\\中草藥樹表.XML").Exists)
            {
                DataTable 中草藥樹表 = new DataTable("中草藥樹表");
                資料表載入(中草藥樹表);
                控制元件非同步處理(() => 玄龍戲珠無級樹(中草藥樹表));
            }