一、使用線程的理由
1、可以使用線程將代碼同其他代碼隔離,提高應(yīng)用程序的可靠性。
2、可以使用線程來簡化編碼。
3、可以使用線程來實現(xiàn)并發(fā)執(zhí)行。
二、基本知識
1、進程與線程:進程作為操作系統(tǒng)執(zhí)行程序的基本單位,擁有應(yīng)用程序的資源,進程包含線程,進程的資源被線程共享,線程不擁有資源。
2、前臺線程和后臺線程:通過Thread類新建線程默認為前臺線程。當所有前臺線程關(guān)閉時,所有的后臺線程也會被直接終止,不會拋出異常。
3、掛起(Suspend)和喚醒(Resume):由于線程的執(zhí)行順序和程序的執(zhí)行情況不可預(yù)知,所以使用掛起和喚醒容易發(fā)生死鎖的情況,在實際應(yīng)用中應(yīng)該盡量少用。
4、阻塞線程:Join,阻塞調(diào)用線程,直到該線程終止。
5、終止線程:Abort:拋出 ThreadAbortException 異常讓線程終止,終止后的線程不可喚醒。Interrupt:拋出 ThreadInterruptException 異常讓線程終止,通過捕獲異??梢岳^續(xù)執(zhí)行。
6、線程優(yōu)先級:AboveNormal BelowNormal Highest Lowest Normal,默認為Normal。
三、線程的使用
線程函數(shù)通過委托傳遞,可以不帶參數(shù),也可以帶參數(shù)(只能有一個參數(shù)),可以用一個類或結(jié)構(gòu)體封裝參數(shù)。
namespace Test
{
class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(TestMethod));
Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod));
t1.IsBackground = true;
t2.IsBackground = true;
t1.Start();
t2.Start("hello");
Console.ReadKey();
}
public static void TestMethod()
{
Console.WriteLine("不帶參數(shù)的線程函數(shù)");
}
public static void TestMethod(object data)
{
string datastr = data as string;
Console.WriteLine("帶參數(shù)的線程函數(shù),參數(shù)為:{0}", datastr);
}
}
}
四、線程池
由于線程的創(chuàng)建和銷毀需要耗費一定的開銷,過多的使用線程會造成內(nèi)存資源的浪費,出于對性能的考慮,于是引入了線程池的概念。線程池維護一個請求隊列,線程池的代碼從隊列提取任務(wù),然后委派給線程池的一個線程執(zhí)行,線程執(zhí)行完不會被立即銷毀,這樣既可以在后臺執(zhí)行任務(wù),又可以減少線程創(chuàng)建和銷毀所帶來的開銷。
線程池線程默認為后臺線程(IsBackground)。
namespace Test
{
class Program
{
static void Main(string[] args)
{
//將工作項加入到線程池隊列中,這里可以傳遞一個線程參數(shù)
ThreadPool.QueueUserWorkItem(TestMethod, "Hello");
Console.ReadKey();
}
public static void TestMethod(object data)
{
string datastr = data as string;
Console.WriteLine(datastr);
}
}
}
五、Task類
使用ThreadPool的QueueUserWorkItem()方法發(fā)起一次異步的線程執(zhí)行很簡單,但是該方法最大的問題是沒有一個內(nèi)建的機制讓你知道操作什么時候完成,有沒有一個內(nèi)建的機制在操作完成后獲得一個返回值。為此,可以使用System.Threading.Tasks中的Task類。
構(gòu)造一個Task<TResult>對象,并為泛型TResult參數(shù)傳遞一個操作的返回類型。
namespace Test
{
class Program
{
static void Main(string[] args)
{
Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000);
t.Start();
t.Wait();
Console.WriteLine(t.Result);
Console.ReadKey();
}
private static Int32 Sum(Int32 n)
{
Int32 sum = 0;
for (; n > 0; --n)
checked{ sum += n;} //結(jié)果太大,拋出異常
return sum;
}
}
}
一個任務(wù)完成時,自動啟動一個新任務(wù)。
一個任務(wù)完成后,它可以啟動另一個任務(wù),下面重寫了前面的代碼,不阻塞任何線程。
namespace Test
{
class Program
{
static void Main(string[] args)
{
Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000);
t.Start();
//t.Wait();
Task cwt = t.ContinueWith(task => Console.WriteLine("The result is {0}",t.Result));
Console.ReadKey();
}
private static Int32 Sum(Int32 n)
{
Int32 sum = 0;
for (; n > 0; --n)
checked{ sum += n;} //結(jié)果溢出,拋出異常
return sum;
}
}
}
六、委托異步執(zhí)行
委托的異步調(diào)用:BeginInvoke() 和 EndInvoke()
namespace Test
{
public delegate string MyDelegate(object data);
class Program
{
static void Main(string[] args)
{
MyDelegate mydelegate = new MyDelegate(TestMethod);
IAsyncResult result = mydelegate.BeginInvoke("Thread Param", TestCallback, "Callback Param");
//異步執(zhí)行完成
string resultstr = mydelegate.EndInvoke(result);
}
//線程函數(shù)
public static string TestMethod(object data)
{
string datastr = data as string;
return datastr;
}
//異步回調(diào)函數(shù)
public static void TestCallback(IAsyncResult data)
{
Console.WriteLine(data.AsyncState);
}
}
}
七、線程同步
1)原子操作(Interlocked):所有方法都是執(zhí)行一次原子讀取或一次寫入操作。
2)lock()語句:避免鎖定public類型,否則實例將超出代碼控制的范圍,定義private對象來鎖定。
3)Monitor實現(xiàn)線程同步
通過Monitor.Enter() 和 Monitor.Exit()實現(xiàn)排它鎖的獲取和釋放,獲取之后獨占資源,不允許其他線程訪問。
還有一個TryEnter方法,請求不到資源時不會阻塞等待,可以設(shè)置超時時間,獲取不到直接返回false。
4)ReaderWriterLock
當對資源操作讀多寫少的時候,為了提高資源的利用率,讓讀操作鎖為共享鎖,多個線程可以并發(fā)讀取資源,而寫操作為獨占鎖,只允許一個線程操作。
5)事件(Event)類實現(xiàn)同步
事件類有兩種狀態(tài),終止狀態(tài)和非終止狀態(tài),終止狀態(tài)時調(diào)用WaitOne可以請求成功,通過Set將時間狀態(tài)設(shè)置為終止狀態(tài)。
1)AutoResetEvent(自動重置事件)
2)ManualResetEvent(手動重置事件)
6)信號量(Semaphore)
信號量是由內(nèi)核對象維護的int變量,為0時,線程阻塞,大于0時解除阻塞,當一個信號量上的等待線程解除阻塞后,信號量計數(shù)+1。
線程通過WaitOne將信號量減1,通過Release將信號量加1,使用很簡單。
7)互斥體(Mutex)
獨占資源,用法與Semaphore相似。
8)跨進程間的同步
通過設(shè)置同步對象的名稱就可以實現(xiàn)系統(tǒng)級的同步,不同應(yīng)用程序通過同步對象的名稱識別不同同步對象。
C#在后臺運行操作:BackgroundWorker的用法
在我們的程序中,經(jīng)常會有一些耗時較長的運算,為了保證用戶體驗,不引起界面不響應(yīng),我們一般會采用多線程操作,讓耗時操作在后臺完成,完成后再進行處理或給出提示,在運行中,也會時時去刷新界面上的進度條等顯示,必要時還要控制后臺線程中斷當前操作。
以前,類似的應(yīng)用會比較麻煩,需要寫的代碼較多,也很容易出現(xiàn)異常。在.net中,提供了一個組件BackgroundWorker就是專門解決這個問題的。BackgroundWorker類允許在單獨的專用線程上運行操作。 耗時的操作(如下載和數(shù)據(jù)庫事務(wù))在長時間運行時可能會導致用戶界面(UI)似乎處于停止響應(yīng)狀態(tài)。如果需要能進行響應(yīng)的用戶界面,而且面臨與這類操作相關(guān)的長時間延遲,則可以使用BackgroundWorker類方便地解決問題。
使用這個組件其實非常簡單,例如,我們做一個類似如下界面的進度條的小例子,在后臺線程中進行耗時運算,同時刷新界面上的進度條。
過程如下:
1.新建一個windows窗體應(yīng)用程序,如:BackgroundWorkerProgressBarDemo
2.拖一個ProgressBar(progressBar1)和一個BackgroundWorker (backgroundWorker1)到Form上。
3.把下面的代碼copy過去就ok了,代碼注釋的很詳細,可以按照需要修改。
namespace BackgroundWorkerProgressBarDemo
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Shown += new EventHandler(Form1_Shown);
// To report progress from the background worker we need to set this property
backgroundWorker1.WorkerReportsProgress = true;
// This event will be raised on the worker thread when the worker starts
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
// This event will be raised when we call ReportProgress
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
void Form1_Shown(object sender, EventArgs e)
{
// Start the background worker
backgroundWorker1.RunWorkerAsync();
}
// On worker thread so do our thing!
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Your background task goes here
for (int i = 0; i <= 100; i++)
{
// Report progress to 'UI' thread
backgroundWorker1.ReportProgress(i);
// Simulate long task
System.Threading.Thread.Sleep(100);
}
}
// Back on the 'UI' thread so we can update the progress bar
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// The progress percentage is a property of e
progressBar1.Value = e.ProgressPercentage;
}
}
}
若要為后臺操作做好準備,請?zhí)砑覦oWork事件的事件處理程序,在此事件處理程序中調(diào)用耗時的操作。
若要開始此操作,請調(diào)用RunWorkerAsync。
若要收到進度更新的通知,請?zhí)幚鞵rogressChanged 事件。
若要在操作完成時收到通知,請?zhí)幚鞷unWorkerCompleted 事件。
注意:
您必須非常小心,確保在 DoWork 事件處理程序中不操作任何用戶界面對象。 而應(yīng)該通過 ProgressChanged 和 RunWorkerCompleted 事件與用戶界面進行通信。
BackgroundWorker 事件不跨 AppDomain 邊界進行封送處理。 請不要使用 BackgroundWorker 組件在多個 AppDomain 中執(zhí)行多線程操作。
如果后臺操作需要參數(shù),請在調(diào)用 RunWorkerAsync 時給出參數(shù)。 在 DoWork 事件處理程序內(nèi)部,可以從 DoWorkEventArgs.Argument 屬性中提取該參數(shù)。
如對本文有疑問,請?zhí)峤坏浇涣髡搲?,廣大熱心網(wǎng)友會為你解答?。?點擊進入論壇