C# 使用多線程的幾種方式


1.Thread

詳細介紹:https://www.cnblogs.com/cheng8/p/16147918.html

使用Thread類通過ThreadStart(無參數)或ParameterizedThreadStart(一個輸入參數)類型的委托創建一個Thread對象,開啟一個新線程,執行該委托傳遞的任務,此時線程尚未處於運行狀態。調用Start()函數啟動線程,當前線程繼續執行。調用Join()函數可以阻塞當前線程,直到調用Join()的線程終止。

Thread類創建的線程默認為前台線程,可以通過IsBackground屬性設置其為前台或后台線程。還可以通過Priority屬性設置線程的優先級。

如需中止線程,調用Abort()方法,在調用該方法的線程上拋出ThreadAbortException異常,以結束該線程。線程內部可以通過try catch捕獲該異常,在catch模塊中進行一些必要的處理,如釋放持有的鎖和文件資源等,還可以通過Thread.ResetAbort()方法阻止線程的中止。但是通常來說,應當慎重使用Abort()方法,如果在當前線程中拋出該異常,其結果是可預測的,但是對於其他線程,它會中斷任何正在執行的代碼,有可能中斷靜態對象的生成,造成不可預測的結果。

Thread thread = new Thread(() =>
{
    Console.WriteLine("Hello");
});
thread.Start();
Console.ReadKey();

2.線程池

ThreadPool類維護一個線程的列表,提供給用戶以執行不同的小任務,減少頻繁創建線程的開銷。ThreadPool的使用比較簡單,只需調用ThreadPool.QueueUserWorkItem()方法,傳遞一個WaitCallback類型的委托,線程池即從池中選擇一個線程執行該任務。

但是線程池的使用也有一些限制:

  • 線程池中的線程均為后台線程,並且不能修改為前台線程
  • 不能給入池的線程設置優先級或名稱
  • 對於COM對象,入池的所有線程都是多線程單元(MTA)線程,許多COM對象都需要單線程單元(STA) 線程
  • 入池的線程只適合時間較短的任務,如果線程需要長時間運行,應使用Thread類創建線程或使用Task的LongRunning選項
for (int i = 0; i < 5; ++i)
    ThreadPool.QueueUserWorkItem(Do);

Console.ReadKey();

 static void Do(Object o)
{
    for (int i = 0; i < 3; i++)
        Console.WriteLine("loop:{0}, thread id: {1}", i, Thread.CurrentThread.ManagedThreadId);
}

3.Parallel類

Parallel和Task類都位於System.Threading.Task命名空間中,是對Thread和ThreadPool類更高級的抽象。Parrallel類有For()、ForEach()、Invoke()三個方法,前兩者在每次迭代中調用相同的代碼,實現了數據並行性,Invoke()允許同時調用不同的方法,實現任務並行性。

For()和ForEach()兩者的用法類似。如下例,調用Parallel.For()方法,實現從0到10的迭代,每次迭代是並行執行的,並且從輸出結果可以看出,執行順序是不能保證的。

ParallelLoopResult result = Parallel.For(0, 10, i =>
{
    Console.WriteLine("i:{0}, thread id: {1}", i, Thread.CurrentThread.ManagedThreadId);
    Thread.Sleep(10);
});

Console.WriteLine("Is completed: {0}", result.IsCompleted);
Console.ReadKey();

 

 通過ParallelLoopState的Break()或Stop()方法,可以提前中斷Parallel.For()的迭代。

ParallelLoopResult result = Parallel.For(0, 100, (i, state) =>
{
    Console.WriteLine("i:{0}, thread id: {1}", i, Thread.CurrentThread.ManagedThreadId);

    if (i > 10)
        state.Break();

    Thread.Sleep(10);
});

Console.WriteLine("Is completed: {0}", result.IsCompleted);
Console.WriteLine("Lowest break iteration: {0}", result.LowestBreakIteration);
Console.ReadKey();

 

 如需同時執行多個不同的任務,可以使用Parallel.Invoke()方法,它允許傳遞一個Action委托數組。

Parallel.Invoke(Func1, Func2, Func3);

4.Task類

相比於Thread類,Task類為控制線程提供了更大的靈活性。Task類可以獲取線程的返回值,也可以定義連續的任務——在一個任務結束結束后開啟下一個任務,還可以在層次結構中安排任務,在父任務中可以創建子任務,這樣就創建了一種依賴關系,如果父任務被取消,子任務也隨之取消。Task類默認使用線程池中的線程,如果該任務需長期運行,應使用TaskCreationOptions.LongRunning屬性告訴任務管理器創建一個新的線程,而不是使用線程池中的線程。
如果在一個Task內部創建了另一個任務,這兩者間就存在父/子的層次結構,當父任務被取消時,子任務也會被取消。如果不希望使用該層次結構,可在創建子任務時選擇TaskCreationOptions.DetachedFromParent。

啟動任務

TaskFactory tf = new TaskFactory();
Task t1 = tf.StartNew(TaskMethod.DoTask, "using a task factory");

Task t2 = Task.Factory.StartNew(TaskMethod.DoTask, "factory via a task");

Task t3 = new Task(TaskMethod.DoTask, "using a task constructor and start");
t3.Start();

var t4 = Task.Run(() => TaskMethod.DoTask("using Run method"));

Console.ReadKey();

class TaskMethod
{
    static object taskLock = new object();
    public static void DoTask(object msg)
    {
        lock (taskLock)
        {
            Console.WriteLine(msg);
            Console.WriteLine("Task id:{0}, Thread id :{1}",
                           Task.CurrentId == null ? "no task" : Task.CurrentId.ToString(),
                           Thread.CurrentThread.ManagedThreadId);
        }
    }
}

接收任務的返回值

對於任務有返回值的情況,可使用Task<TResult>泛型類,TResult定義了返回值的類型,以下代碼演示了調用返回int值的任務的方法。

var t5 = new Task<int>(TaskWithResult, Tuple.Create<int, int>(1, 2));
t5.Start();
t5.Wait();
Console.WriteLine("adder results: {0}", t5.Result);

Console.ReadKey();

 static int TaskWithResult(object o)
{
    Tuple<int, int> adder = (Tuple<int, int>)o;
    return adder.Item1 + adder.Item2;
}

同步調用

調用Task類的RunSynchronously()方法,可以實現同步調用,直接在當前線程上調用該任務。

TaskMethod.DoTask("Just Main thread");
Task t1 = new Task(TaskMethod.DoTask, "using Run Sync");
t1.RunSynchronously();

class TaskMethod
{
    static object taskLock = new object();
    public static void DoTask(object msg)
    {
        lock (taskLock)
        {
            Console.WriteLine(msg);
            Console.WriteLine("Task id:{0}, Thread id :{1}",
                           Task.CurrentId == null ? "no task" : Task.CurrentId.ToString(),
                           Thread.CurrentThread.ManagedThreadId);
        }
    }
}

指定連續任務

TaskFactory tf = new TaskFactory();
Task t1 = tf.StartNew(() =>
{
    Console.WriteLine("Current Task id = {0}", Task.CurrentId);
    Console.WriteLine("執行任務1\r\n");
    Thread.Sleep(10);
});

Task t2 = t1.ContinueWith((t) =>
{
    Console.WriteLine("Last Task id = {0}", t.Id);
    Console.WriteLine("Current Task id = {0}", Task.CurrentId);
    Console.WriteLine("執行任務2\r\n");
    Thread.Sleep(10);
});

Task t3 = t2.ContinueWith(delegate (Task t)
{
    Console.WriteLine("Last Task id = {0}", t.Id);
    Console.WriteLine("Current Task id = {0}", Task.CurrentId);
    Console.WriteLine("執行任務3\r\n");
}, TaskContinuationOptions.OnlyOnRanToCompletion);

Console.ReadKey();

 

從執行結果可以看出,任務1,2,3被順序執行,同時通過 TaskContinuationOptions 還可以指定何種情況下繼續執行該任務,常用的值包括OnlyOnFaulted, OnlyOnCanceled, NotOnFaulted, NotOnCanceled等。如將上例中的OnlyOnRanToCompletion改為OnlyOnFaulted,任務2結束之后,任務3將不被執行。
對於ContinueWith()的使用,MSDN演示了更加優雅的“流式”調用方法:
 var backgroundScheduler = TaskScheduler.Default;  
   var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();  
   Task.Factory.StartNew(delegate { DoBackgroundComputation(); },  
                         backgroundScheduler).  
   ContinueWith(delegate { UpdateUI(); }, uiScheduler).  
                ContinueWith(delegate { DoAnotherBackgroundComputation(); },  
                             backgroundScheduler).  
                ContinueWith(delegate { UpdateUIAgain(); }, uiScheduler);  

5.BackgroundWorker控件

https://docs.microsoft.com/zh-cn/dotnet/api/system.componentmodel.backgroundworker?view=netframework-4.7.2

除了上述四類直接操作多線程的方法,C#還提供了BackgroundWorker控件幫助用戶更簡單、安全地實現多線程運算。該控件提供了DoWork, ProgressChanged 和 RunWorkerCompleted事件,為DoWork添加事件處理函數,再調用RunWorkerAsync()方法,即可創建一個新的線程執行DoWork任務。ProgressChanged和RunWorkerCompleted事件均在UI線程中執行,添加相應的處理函數,即可完成任務線程與UI線程間的交互,可用於顯示任務的執行狀態(完成百分比)、執行結果等。同時,該控件還提供了CancleAsync()方法,以中斷線程的執行,需注意的是,調用該方法后,只是將控件的CancellationPending屬性置True,用戶需在程序執行過程中查詢該屬性以判定是否應中斷線程。
 
原文:https://www.jianshu.com/p/36a65838fe46


免責聲明!

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



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