C#多線程,基礎知識很重要


本文通過介紹C#多線程的用法(基礎玩法),附加介紹一下WinForm里邊跨線程訪問UI的方法

如圖,就是這么一個簡單的界面,每個按鈕下面一個方法,分別設置文本框里邊的內容,那么,開始吧!

先介紹一下WinForm的線程模型:WinForm 是通過調用Windows API 的GetMessage Or PeekMeeage來處理其他線程發送過來的消息,這些消息存儲在系統的一個消息隊列中,創建主界面的線程就是主線程(UI線程),UI線程負責消費該消息隊列中的消息。

WinForm框架中有一個ISynchronizeInvoke接口,所有的UI元素都繼承自該接口,接口中的InvokeRequired屬性表示了當前線程是否是創建它的線程,接口中的BeginInvoke or Invoke 負責將消息發送到消息隊列,這樣UI線程就能夠正確的訪問它了。

那么,首先看代碼片段一:里邊就實現了將設置文本框內容的消息發送到了消息隊列

private void SetMessage(string message)
        {
            if (this.txtMsg.InvokeRequired)
            {
                //BeginInvoke or Invoke 負責將消息發送到消息隊列
                this.txtMsg.BeginInvoke(new Action<string>((msg) =>
                {
                    this.txtMsg.Text = msg;
                }), message);
            }
            else
            {
                this.txtMsg.Text = message;
            }

        }

代碼片段二Thread:Thread可能是用的最多的了,也是最早的框架里邊就有的。這種寫法很簡單,也很方便,需要提一下的就是IsBackground屬性,IsBackground=true表示為后台線程,應用程序退出,哪怕任務沒有執行完,也會退出;IsBackground=false表示為前台線程,默認為false,應用程序退出,只要任務還沒有執行完,進程就不會結束。

private void btnThread_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(() =>
            {
                SetMessage("Thread 跨線程訪問UI");
            });
            //IsBackground=true表示為后台線程 應用程序退出 哪怕任務沒有執行完 也會退出
            //IsBackground=false表示為后台線程 默認為false 應用程序退出 只要任務還沒有執行完 進程就不會結束
            thread.IsBackground = true;
            thread.Start();
        }

代碼片段三ThreadPool:ThreadPool是微軟為了避免開發人員,無節制的使用線程,而提供的一個線程管理類

private void btnThreadPool_Click(object sender, EventArgs e)
        {
            //線程池 是微軟為了避免開發人員 無節制的使用線程 而提供的一個線程管理類
            ThreadPool.QueueUserWorkItem((obj) =>
            {
                SetMessage("ThreadPool 跨線程訪問UI");
            }, null);
        }

代碼片段四Task:Task有很多的優勢,也是后面高版本才推出來的,推薦使用。Task與Thread的區別就是:Task使用的是線程池中的線程,Task較之線程池的優勢是:

1.Task支持取消,完成,失敗通知等交互性操作

2.Task支持線程執行的先后次序

private void btnTask_Click(object sender, EventArgs e)
        {
            /* Task與Thread的區別就是:Task使用的是線程池中的線程
             * Task較之線程池的優勢是:
             * 1.Task支持取消,完成,失敗通知等交互性操作
             * 2.Task支持線程執行的先后次序*/
            Task.Factory.StartNew(() =>
            {
                SetMessage("Task 跨線程訪問UI");
            });

            Task task = new Task(()=> {
                SetMessage("Task 跨線程訪問UI");
            });
            task.Start();
        }

代碼片段五BackgroundWorker:BackgroundWorker內部是通過線程池實現的,通過事件提供了跨線程訪問UI的能力,這個做CS開發,是用的最多的,說白了,太好用。

//BackgroundWorker 內部是通過線程池實現的
        //BackgroundWorker 通過事件提供了跨線程訪問UI的能力
        BackgroundWorker _bgw = new BackgroundWorker();

        private void btnBackgroundWorker_Click(object sender, EventArgs e)
        {
            _bgw.WorkerReportsProgress = true;
            _bgw.WorkerSupportsCancellation = true;
            _bgw.DoWork += _bgw_DoWork; ;
            _bgw.ProgressChanged += _bgw_ProgressChanged;
            _bgw.RunWorkerCompleted += _bgw_RunWorkerCompleted;

            if (!_bgw.IsBusy)
            {
                _bgw.RunWorkerAsync();
            }
        }

        private void _bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.txtMsg.Text = "BackgroundWorker 跨線程訪問UI";//注意這里是直接訪問UI
        }

        private void _bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.txtMsg.Text = e.UserState.ToString();//注意這里是直接訪問UI
        }

        private void _bgw_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                _bgw.ReportProgress(i, $"{i}秒");
            }
        }

代碼片段六SynchronizationContext:SynchronizationContext同步上下文在通訊中充當傳輸者的角色,實現功能就是一個線程和另外一個線程的通訊,這個在跨線程一次性要更新很多的UI控件的時候,非常的適用。

//SynchronizationContext 在通訊中充當傳輸者的角色,實現功能就是一個線程和另外一個線程的通訊
        SynchronizationContext _syncContext;

        private void btnSynchronizationContext_Click(object sender, EventArgs e)
        {
            _syncContext = SynchronizationContext.Current;
            Thread thread = new Thread(() =>
            {
                if (_syncContext != null)
                {
                    SendOrPostCallback callBack = (obj) =>
                    {
                        //在某個子線程里 一次性要更新很多UI控件的時候 用這個方法 很nice
                        this.txtMsg.Text = "SynchronizationContext 跨線程訪問UI";
                    };
                    _syncContext.Post(callBack, null);//異步
                    //_syncContext.Send(callBack, null);//同步
                }
            });
            thread.IsBackground = true;
            thread.Start();
        }

合並之后的代碼為:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ThreadChapter
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnThread_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(() =>
            {
                SetMessage("Thread 跨線程訪問UI");
            });
            //IsBackground=true表示為后台線程 應用程序退出 哪怕任務沒有執行完 也會退出
            //IsBackground=false表示為后台線程 默認為false 應用程序退出 只要任務還沒有執行完 進程就不會結束
            thread.IsBackground = true;
            thread.Start();
        }

        /*
         * WinForm 是通過調用Window API 的GetMessage Or PeekMeeage來處理其他線程發送過來的消息,
         * 這些消息存儲在系統的一個消息隊列中,創建主界面的線程就是主線程(UI線程),UI線程負責處理該消息隊列
         */

        private void SetMessage(string message)
        {
            if (this.txtMsg.InvokeRequired)
            {
                //BeginInvoke or Invoke 負責將消息發送到消息隊列
                this.txtMsg.BeginInvoke(new Action<string>((msg) =>
                {
                    this.txtMsg.Text = msg;
                }), message);
            }
            else
            {
                this.txtMsg.Text = message;
            }

        }

        private void btnThreadPool_Click(object sender, EventArgs e)
        {
            //線程池 是微軟為了避免開發人員 無節制的使用線程 而提供的一個線程管理類
            ThreadPool.QueueUserWorkItem((obj) =>
            {
                SetMessage("ThreadPool 跨線程訪問UI");
            }, null);
        }

        private void btnTask_Click(object sender, EventArgs e)
        {
            /* Task與Thread的區別就是:Task使用的是線程池中的線程
             * Task較之線程池的優勢是:
             * 1.Task支持取消,完成,失敗通知等交互性操作
             * 2.Task支持線程執行的先后次序*/
            Task.Factory.StartNew(() =>
            {
                SetMessage("Task 跨線程訪問UI");
            });
        }


        //BackgroundWorker 內部是通過線程池實現的
        //BackgroundWorker 通過事件提供了跨線程訪問UI的能力
        BackgroundWorker _bgw = new BackgroundWorker();

        private void btnBackgroundWorker_Click(object sender, EventArgs e)
        {
            _bgw.WorkerReportsProgress = true;
            _bgw.WorkerSupportsCancellation = true;
            _bgw.DoWork += _bgw_DoWork; ;
            _bgw.ProgressChanged += _bgw_ProgressChanged;
            _bgw.RunWorkerCompleted += _bgw_RunWorkerCompleted;

            if (!_bgw.IsBusy)
            {
                _bgw.RunWorkerAsync();
            }
        }

        private void _bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.txtMsg.Text = "BackgroundWorker 跨線程訪問UI";//注意這里是直接訪問UI
        }

        private void _bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.txtMsg.Text = e.UserState.ToString();//注意這里是直接訪問UI
        }

        private void _bgw_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                _bgw.ReportProgress(i, $"{i}秒");
            }
        }

        //SynchronizationContext 在通訊中充當傳輸者的角色,實現功能就是一個線程和另外一個線程的通訊
        SynchronizationContext _syncContext;

        private void btnSynchronizationContext_Click(object sender, EventArgs e)
        {
            _syncContext = SynchronizationContext.Current;
            Thread thread = new Thread(() =>
            {
                if (_syncContext != null)
                {
                    SendOrPostCallback callBack = (obj) =>
                    {
                        //在某個子線程里 一次性要更新很多UI控件的時候 用這個方法 很nice
                        this.txtMsg.Text = "SynchronizationContext 跨線程訪問UI";
                    };
                    _syncContext.Post(callBack, null);//異步
                    //_syncContext.Send(callBack, null);//同步
                }
            });
            thread.IsBackground = true;
            thread.Start();
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM