[c#]執行緒(thread)和執行緒集區(threadpool)的使用

執行緒(thread)和執行緒集區(threadpool)的使用

 

前言

其實筆者很少使用到執行緒,而且微軟也表明不建議在去操作thread,但是因為很多舊專案還是會用到,所以一旦你去面試的時候,面試官一直在問甚至考卷有考到thread的時候,有可能就是一個舊專案在找人維護,其實執行緒或者平行或者非同步都是有同一個目的,就是希望可以多工執行,在傳統c#的思維上是同步的,是一步一步執行下來的,如果我們希望能同時執行多件事情,可以發揮硬體真正的效能,並且加快處理的速度,就可以用到執行緒(但是你有其他更好的選擇,盡量不要再使用執行緒了),需注意一下,一旦你使用了執行緒,一條執行緒就會佔用大約1MB的記憶體,所以頻繁的使用執行緒不是一個好主意哦,接下來就開始介紹一下如何使用執行緒吧,範例求方便直接使用linq pad來做操作。

導覽

  1. 什麼情況下,才考慮使用執行緒的方式
  2. 如何使用執行緒
  3. 如何傳遞參數,如何中斷
  4. 前景執行緒和背景執行緒的差別
  5. 判斷執行緒是否還活著
  6. 共享變數造成錯亂的處理方式
  7. 執行緒集區
  8. 結論

 

什麼情況下,使用執行緒可能是比較好的

  1. 長時間的工作,這時候可以用一條執行緒來執行這個工作
  2. 希望工作一定得完成,不受任何狀況而中斷(以前景執行緒來執行,執行緒集區永遠都是背景執行緒)
  3. 希望能在某些情況下手動中斷執行緒(呼叫Abort的方式)

 

如何使用執行緒

void Main()
{
	var task1=new Thread(startTask1); //開啟一條執行緒,假設這個要求一秒後才回覆
	task1.Start();
	Console.WriteLine("task2");
}

static void startTask1()
{
	Thread.Sleep(1000);
	Console.WriteLine("task1");
}

結果為task2跑得比task1快,可以看到我們已經成為多工執行,task2不需要等待task1執行完畢,就直接先執行了

如果熟悉javascript的,應該會覺得這不就是非同步嗎,其實那麼多名詞不管是執行緒或並行或非同步都是要不卡住某個動作而要多工執行的概念,以加快運行速度,如果下一個變數必須要等到task1的變數回來,才能繼續執行下去的話,我們可以使用join,去等待這條執行緒結束

void Main()
{
	var task1=new Thread(startTask1);
	task1.Start();
	Console.WriteLine("task2");
	task1.Join(); //等待task1這條執行緒結束才能繼續執行,也就是在此變成同步的意思
	Console.WriteLine("wait for task1 end");
}

 

如何傳遞參數,如何中斷

假設我現在想要執行秒數是用參數傳進去的,那就可以改成如下,需要注意被呼叫的方法參數只能用object,之後再自行做轉型。

void Main()
{
	var task1=new Thread(startTask1);
	task1.Start(1000); //在start的地方傳入參數
	Console.WriteLine("task2");
	task1.Join();
	Console.WriteLine("wait for task1 end");
}

static void startTask1(object param1)
{
	Thread.Sleep((int) param1);
	Console.WriteLine("task1");
}

接下來如果我想要中斷某條執行緒的任何,只要使用Abort()就行了

void Main()
{
	var task1 = new Thread(startTask1);
	task1.Start(1000); //在start的地方傳入參數
	Console.WriteLine("task2");
	task1.Abort(); //直接中斷不執行此任務
	task1.Join();
	Console.WriteLine("wait for task1 end");
}

static void startTask1(object param1)
{
	Thread.Sleep((int)param1);
	Console.WriteLine("task1");
}

結果

task2
wait for task1 end

 

前景執行緒和背景執行緒的差別

前景執行緒也就是當我們應用結束的時候,此執行緒還是會繼續執行到結束,背景執行緒的話則會隨著應用中斷就結束,所以如果我們有些任務不管什麼情況都要執行完畢,就得使用前景執行緒,而如果沒有特別設定的話,預設就是前景執行緒,底下是一個示例,如何改成背景執行緒的方式

void Main()
{
	var task1 = new Thread(startTask1);
	task1.IsBackground=true; //改成背景執行緒
	task1.Start(1000); 
	Console.WriteLine("task2");
}

static void startTask1(object param1)
{
	Thread.Sleep((int)param1);
	Console.WriteLine("task1");
}

 

判斷執行緒是否還活著

如果我們有某些需求,需得判斷執行緒活不活著的狀況,而做後續處理的話,可以使用IsAlive

void Main()
{
	var task1 = new Thread(startTask1);
	task1.IsBackground = true; //改成背景執行緒
	task1.Start(1000);
	Console.WriteLine("task2");
	if (task1.IsAlive)//判斷是否還活著
	{
		Console.WriteLine("Alive");
	}
}

static void startTask1(object param1)
{
	Thread.Sleep((int)param1);
	Console.WriteLine("task1");
}

 

共享變數造成錯亂的處理方式

如果我們在多個執行緒然後操作到同一個變數,就可能會導致錯亂,其實也就是有個專業術語來形容,thread unsafe的概念,所以如果我們有共享到同一個變數,就得在要操作到同樣變數的時候,在那個地方改成同步的,也就是用lock的方式

void Main()
{
	int j = 0;
	var task1 = new Thread(startTask);
	var task2 = new Thread(startTask);
	task1.Start(300);
	task2.Start(200);
	
	void startTask(object param1)
	{	
		j++;
		Thread.Sleep((int)param1);
		j.Dump();
	}
}

結果

2
2

但正確應該是1,2,那改成lock吧

void Main()
{
	int j = 0;
	var task1 = new Thread(startTask);
	var task2 = new Thread(startTask);
	task1.Start(300);
	task2.Start(200);
	
	void startTask(object param1)
	{
		lock (this) 在此用lock改成同步的,確保thread safe
		{
			j++;
			Thread.Sleep((int)param1);
			j.Dump();
		}
	}
}

 

執行緒集區

其實這種概念很像我們在使用ado.net的connection pool的概念,不要馬上用完就馬上去關閉,而是交給執行緒集區去處理,執行緒集區會視狀況幫你掌控好資源分配,而如果我們使用執行緒的話,每次新開一條執行緒和關閉執行緒都會浪費一點時間,所以優先考慮使用執行緒集區來讓底層幫我們做資源分配,才是上上策哦。

void Main()
{
	ThreadPool.QueueUserWorkItem(new WaitCallback(startTask),1000);
	
	void startTask(object param1)
	{
		Thread.Sleep((int)param1);
		Console.WriteLine("task1");
	}
}

 

結論

這篇只是做個記錄,不然老是記不起來,曾經跟友人聊到這個部份,真的是支支吾吾的忘記了,一再的讀一再的忘,所以乾脆自己來寫篇筆記,但是其實我們在.net 4.0之後,應該使用TPL來取代thread了,所以此篇就僅供有維護到舊專案參考用的囉。

參考來源

http://huan-lin.blogspot.com/2013/06/csharp-notes-multithreading-4-thread.html