線程(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線程;至少目前我是這樣理解的,因此,初涉這個領域時不小心就會犯線程相互交涉而發生錯誤;更具體查看微軟或網絡上相關資料就不贅述了。

參考網址:
http://hi.baidu.com/jok607/blog/item/393746e72f513125b8382002.html


http://www.cnblogs.com/mashang/archive/2009/08/01/1536730.html

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

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

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(返回值));將提示錯誤。

原因參考網址:
http://technet.microsoft.com/zh-cn/library/system.asynccallback(zh-tw).aspx

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

使用組件BackgroundWorker 類

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

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

Func<stringbool> 邏輯 = delegate(string 年信息)

{

    if (年信息.Contains("11")) return false;

    return 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> 顯示 = (參數)=>
{

};

顯示(/*在需要調用的地方寫這行代碼*/);

點擊打開鏈接<多線程描述>

並行處理:using System.Threading.Tasks;/*並行運算*/            Parallel.Invoke(() =>{/*代碼塊*/}); 

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章