1. 多線程
(1) 進程(Process):是WIndows系統中的一個基本概念,它包含着一個運行程序所需要的資源,進程之間是相對獨立的,一個進程無法直接訪問另一個進程的數據(除非利用分布式計算方法),一個進程運行的失敗也不會影響其他進程的運行,windows系統就是利用進程把工作划分為多個獨立的區域,進程可以理解為一個程序的基本邊界。
1)要解 決的問題:為了使程序能夠並發執行(要並發處理就要隔離進程,使進程獨立,即每個進程有屬於自己的數據段,程序段,進程控制塊)
2)進程是隔離不同應用程序的一種資源
3)進程Demo,代碼如下:
static void Main(string[] args)
{
//操作進程:GetCurrentProcess讀取當前進程
Console.WriteLine(Process.GetCurrentProcess());
//獲取操作系統中的所有進程
var process = Process.GetProcesses();
foreach (var item in process)
{
//輸出所有操作系統的名稱
Console.WriteLine(item.ProcessName);
}
//開啟一個應用程序的進程
Process.Start("iexplore.exe", "http://www.cnblogs.com/hanyinglong");
var p = Process.Start("notepad.exe");
//殺掉一個線程
Thread.Sleep(3000);
p.Kill();
}
(2) 線程:線程就是一個代碼段的執行流
1)是Windows任務調度的最小單位,線程是程序中的一個執行流,每個線程都有自己的專有寄存器(棧指針,程序計數器等),但代碼區是共享的,即不同的線程可以執行同樣的函數。
2)解決問題:進程是一個資源的擁有者,因為在進程的創建,撤銷和切換的過程中,系統必須為自付出比較大的開銷,限制了並發程序的進一步提高
3)一個進程可以有多個線程
4)CPU的核心切換的是線程
5)操作系統:一個CPU同一時間只能執行一個線程,多核,
6)操作系統切換線程的時候需要將線程的資源,狀態都要保存起來。
7)線程創建的時候需要線程的控制塊(1M),線程棧,線程寄存器。
(3) 托管代碼和非托管代碼的區別
(4) 線程Demo,創建一個控制台應用程序,代碼如下:
namespace ThreadDemo { //當前這個程序的代碼是執行在主線程上面的 class Program { static void Main(string[] args) { //打印出當前主線程的信息 Thread currentThread= Thread.CurrentThread; //打印出主線程的ID,ManagedThreadId是托管線程的一個唯一編號 Console.WriteLine("當前默認的主線程的ID是" + currentThread.ManagedThreadId); //創建一個線程,傳遞一個ThreadStart參數,這個參數轉到定義可以看到是一個委托 //創建一個線程對象,並沒有真正的分配線程 Thread threadDemo = new Thread(ThreadDemoMethod); //只有調用此方法的時候,才是真正的告訴操作系統線程准備好,請操作系統分配線程並且執行 threadDemo.Start(); //啟動線程來執行 Console.WriteLine("主線程執行結束"); Console.ReadKey(); } static void ThreadDemoMethod() { Console.WriteLine("另一個線程正在執行,線程的ID是{0}", Thread.CurrentThread.ManagedThreadId); } } }
這段代碼的執行結果是:
當前默認的主線程的ID是1
主線程執行結束
另一個線程正在執行,線程的ID是3
(5) 應用程序域
1)它提供安全而通用的處理單元,公共語言運行庫(CLR)可以使用它來提供應用程序之間的隔離。您可以在具有同等隔離級別(存在於單獨的進程中)的單個進程中運行幾個應用程序域,而不會造成進程間調用或者進程間切換等方面的額外開銷,在一個進程內運行多個應用程序的能力顯著增強了服務器的可伸縮性
2)應用程序域里面有內存分配的堆,也提供了一種代碼安全的邊界(兩個應用程序域之間的代碼是相互隔離的),提供了異常的處理
3)一個應用程序域可以跑多個線程
4)一個線程同一時間只能運行在一個應用程序域中,但是一個應用程序域可以同時擁有多個線程
5)應用程序域Demo
static void Main(string[] args)
{
//打印當前應用程序域
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
//創建應用程序域
AppDomain domainDemo = AppDomain.CreateDomain("韓迎龍");
//讓當前應用程序域啟動執行一個exe程序(可以包含執行一個exe,但是可以有多個程序集dll)
//第一步:將其他項目中的exe文件和pdf文件拷貝一份放到我們這個項目下面
//第二步:將上面拷貝進來的exe文件右鍵屬性,修改復制到輸出目錄為始終復制
domainDemo.ExecuteAssembly("搖獎機.exe");
}
6)執行結果,如圖所示:
(6) 簡單線程詳細說明
1)線程的調度是由操作系統來操作的,我們的操作只是建議
2)threadDemo.IsBackground = true;
-> 設置當前線程的一個類型,默認是前台線程
-> true代表設置一個后台線程,當前台線程都關閉之后,當前進程就直接關閉了
-> false代表設置一個前台線程,
3)線程設置Demo
class Program { static void Main(string[] args) { Thread threadDemo = new Thread(DemoThreadMethod); //設置當前線程的優先級,只是建議操作系統,給我當前的這個線程,優先級高點 threadDemo.Priority = ThreadPriority.Highest; //如果設置成前台線程,那么就必須當前線程執行完畢之后,整個進程才會推出 threadDemo.IsBackground = true; threadDemo.Start(); Thread.Sleep(3000); //關閉線程 //threadDemo.Abort(); //將當前執行此代碼的線程阻塞,等待線程執行完畢 threadDemo.Join(1000); //給線程起一個名字 threadDemo.Name = "線程"; Console.WriteLine("主線程執行完畢"); } static void DemoThreadMethod() { while (true) { Console.WriteLine(DateTime.Now.ToString()); } } }
(7) 帶參數的線程說明
1)ParameterizedThreadStart執行方法
->查看Reflect: public delegate void ParameterizedThreadStart(object obj);
2)線程的委托都是沒有返回值的,因為我們不知道線程什么時候結束
3)帶參數的線程Demo
class Program
{
static void Main(string[] args)
{
//啟動一個帶參數的線程
Thread threadDemo = new Thread(DemoParamets);
//給線程指定的方法傳遞參數
//可以傳遞數組
threadDemo.Start("韓迎龍");
Thread.Sleep(2000);
Console.ReadKey();
}
static void DemoParamets(object data)
{
Console.WriteLine(data.ToString());
}
}
4)將上面這段代碼在Reflect中查看代碼:如圖所示:
2. 線程中如何訪問控件
(1) 新建一個Winform應用程序,起名為:MultiThread,給這個新建的WinForm窗體添加一個文本框控件和一個Button按鈕
(2)Demo代碼
public partial class Form1 : Form { public Form1() { InitializeComponent(); //如果不寫的話會報這個錯誤:線程間操作無效: 從不是創建控件“txtMSg”的線程訪問它。 //還有一種方式解決將在下面說到 Control.CheckForIllegalCrossThreadCalls = false; } private void btnThread_Click(object sender, EventArgs e) { //這是一種寫法 //new Thread(() => //{ // txtMSg.Text = DateTime.Now.ToString(); //}).Start(); //第二種說法 Thread thread = new Thread(ChangeTxt); thread.Start(); } public void ChangeTxt() { txtMSg.Text = DateTime.Now.ToString(); } }
3. 使用委托實現兩個界面之間數據的交互案例
(1) 新建一個Winform程序,將Form1窗體作為主窗體,在新建一個窗體作為子窗體,給父窗體添加一個Button和一個TextBox控件。給子窗體中添加一個Button控件和一個TextBox控件,項目樣式在后面有截圖:
(2)主窗體中的代碼如下: public partial class Form1 : Form { public Form1() { InitializeComponent(); //如果不寫的話會報這個錯誤:線程間操作無效: 從不是創建控件“txtMSg”的線程訪問它。 //還有一種方式解決將在下面說到 Control.CheckForIllegalCrossThreadCalls = false; } private void btnThread_Click(object sender, EventArgs e) { while (true) { Thread thread = new Thread(ChangeTxt); thread.Start(); } } public void ChangeTxt() { txtMSg.Text = DateTime.Now.ToString(); } private void btnStart_Click(object sender, EventArgs e) { //使用委托實現 FrmChild frm = new FrmChild(); //把當前主窗體的應用添加到this中 frm.ParentFrm = this; frm.AfterTxtChange = new SetTextDel(SetText); frm.Show(); } //添加一個方法給主窗體的控件賦值 public void SetText(string str) { txtMSg.Text = str; } } (3)子窗體中的代碼如下: //使用委托實現這個功能 public delegate void SetTextDel(string str); public partial class FrmChild : Form { //定義了一個父窗體的變量 public Form1 ParentFrm; //定義一個委托類的實例 public SetTextDel AfterTxtChange; public FrmChild() { InitializeComponent(); } private void btnParent_Click(object sender, EventArgs e) { string childtxt = txtChildMsg.Text; //第一種方法 ////將值同步到主窗體上面 //this.ParentFrm.SetText(childtxt); //第二種方法 AfterTxtChange(childtxt); } }
(4)實現結果如圖所示:
4. 總結
(1) 為什么要用多線程
1)讓計算機"同時"做多件事情,節約能源。
2)多線程可以讓一個程序"同時"處理多個事情。
3)后台運行程序,提高程序的運行效率,也不會是主界面出現無響應的情況
(2)產生一個線程的四個步驟
1)編寫產生線程所要執行的方法。
2)引入System.Threading命名空間。
3)實例化Thread類,並傳入一個指向線程所要運行方法的委托(這時候線程已經產生,但是還沒有運行)。
4)調用Thread實例的Start方法,標記該線程可以被CPU執行了,但是具體的執行時間由CPU決定。
(3)注意"方法重入"的問題
1)所謂的方法重入,是一個有關多線程編程的概念,程序中多個線程同時運行時,就可能發生同一個方法被多個線程同時調用的情況,當這個方法中存在一些非線程安全的代碼,方法重入就會導致數據不一致的情況,這是一個嚴重的Bug。
(4)進程和線程
1)一個進程至少有一個線程
2)同一個進程中的多個線程之間可以"並發"執行。
3)線程是程序中的一個執行流,每個線程都有自己的專有寄存器(棧指針、程序計數器等),但代碼區是共享的,即不同的線程可以執行同樣的函數。多線程是指程序中包含多個執行流,即在一個程序中可以同時運行多個不同的線程來執行不同的任務(代碼),也就是說允許單個程序創建多個並行執行的線程來完成各自的任務。
(5)前台線程和后台線程
1)前台線程:只有所有的前台線程都關閉才能完成程序關閉。
2)后台線程:只有所有的前台線程結束,后台線程自動結束。
(6).NET中如何實現多線程?
1)線程肯定也是要執行一段代碼的。所以要產生一個線程,必須先為該線程寫一個方法,這個方法中的代碼就是該線程運行所要執行的代碼(找一個人來做這件事情)
2)線程啟動時,通過委托調用該方法.(線程啟動時,調用傳過來的委托,委托就會執行相應的代碼,實現線程執行的方法)。
(7)Thread類的一些成員
1)Start()啟動線程
2)Abort()終止線程
3)Thread.Sleep(100)靜態方法,可以使當前線程停止一段時間在運行
4)Name線程名稱
5)Thread.CurrentThread獲得當前的線程引用