C#多線程與異步


1、什么是異步同步

如果一個方法被調用,調用者需要等待該方法被執行完畢之后才能繼續執行,則是同步。

如果方法被調用后立刻返回,即使該方法是一個耗時操作,也能立刻返回到調用者,調用者不需要等待該方法,則稱之為異步。

異步編程需要用到Task任務函數,不返回值的任務由 System.Threading.Tasks.Task 類表示。返回值的任務由 System.Threading.Tasks.Task<TResult> 類表示,該類從Task 繼承。

C#提供了基於任務的異步編程方法(TPL),更多資料在《.NET 中的並行編程》https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-programming/

先來個例子看下Task的基本用法。

static void Main(string[] args)
{
    var task = new Task(() =>
    {
       Console.WriteLine($"hello, task的線程ID為{Thread.CurrentThread.ManagedThreadId}");
       Console.WriteLine("task start....");
       Thread.Sleep(2000);
       Console.WriteLine("task end....");
    });
    task.Start();
    Console.WriteLine("main start....");
    Thread.Sleep(1000);
    Console.WriteLine("main end....");
    Console.ReadLine();
}

運行結果為:

 2、Task的三種啟動方法

通過new方法創建,然后start啟動。

通過Task.Factory.StartNew創建。

通過Task.Run創建。

Task task1 = new Task(() =>
Console.WriteLine($"hello, task 1的線程ID{Thread.CurrentThread.ManagedThreadId}");});
task1.Start();

Task task2 = Task.Run(() =>
{Console.WriteLine($"hello, task 2的線程ID{Thread.CurrentThread.ManagedThreadId}");});

Task task3 = Task.Factory.StartNew(() =>
{Console.WriteLine($"hello,task 3的線程ID為{Thread.CurrentThread.ManagedThreadId}");});

3、Task阻塞與等待

如果要等待任務完成可以使用Task.Wait方法,使用WaitAll方法等待所有任務完成。如果等待多個任務但是只需要任何一個任務完成就可以執行下一步,則可以使用WaitAny。

static void Main(string[] args)
{
     var t1 = Task.Run(() =>
     {
         Thread.Sleep(100);
         Console.WriteLine($"t1線程ID為{ Thread.CurrentThread.ManagedThreadId}");
     });

     var t2 = Task.Run(() =>
     {
        Thread.Sleep(100);
        Console.WriteLine($"t2線程ID為{ Thread.CurrentThread.ManagedThreadId}");
     });
     // t1.Wait();
    var t3 = Task.Run(() =>
    {
        Console.WriteLine($"t3線程ID為{Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine("t3 start....");
        Thread.Sleep(2000);
        Console.WriteLine("t3 end....");
     });
     
Console.WriteLine(
"main start...."); Thread.Sleep(1000); Console.WriteLine("main end...."); Console.ReadLine(); }

運行效果:

 如果打開t1.Wait(); 如下t1先執行。

 如果t1.Wait()改為:Task.WaitAll(t1, t2);輸出如下,先等待t1和t2執行完畢。

如果需要某個任務先執行完再運行主線程,則使用RunSynchronously就可以同步執行Task任務。下面的例子就是在當前的調度器上同步的執行任務t1。

var t1 = new Task(() =>
{ Thread.Sleep(100); Console.WriteLine($"t1線程ID為{ Thread.CurrentThread.ManagedThreadId}"); }); t1.RunSynchronously();

  Console.WriteLine($"主線程ID為{ Thread.CurrentThread.ManagedThreadId}");
  Console.ReadLine();

 4、任務的狀態和返回值

Task<Tresult>獲取異步任務的返回值。

var t1 = Task.Run(() =>
{
   Thread.Sleep(100);
   Console.WriteLine($"t1線程ID為{ Thread.CurrentThread.ManagedThreadId}");
   return "t1 return";
});
Console.WriteLine($"t1 return value {t1.Result}");

Task<Tresult>會阻塞任務直到任務返回,Task.Result給出任務的返回值給調用它的任務,下面例子顯示了Result等待任務完成。

 var cts = new CancellationTokenSource();
 Task<int> t1 = new Task<int>(() =>
 {
    try
    {
      //while (!cts.IsCancellationRequested)
       {
          //cts.Token.ThrowIfCancellationRequested();
          Console.WriteLine($"t1 start 線程ID為{ Thread.CurrentThread.ManagedThreadId}");
          Thread.Sleep(1000);
          Console.WriteLine($"t1 end 線程ID為{ Thread.CurrentThread.ManagedThreadId}");
        }
        return 1;
     }
     catch (Exception ex)
     {
         Console.WriteLine("---Exception---");
         return 0;
      }        
 }, cts.Token);
 t1.Start();
 Console.WriteLine("---end---{0}", t1.Result);

 輸出結果如下,t.Result為1.

 t.Result會導致阻塞直至等待的任務完成返回。如果使用返回值,那么異步方法中方法簽名返回值為Task<T>,代碼中的返回值也要為T。

static void Main(string[] args)
{
   var cts = new CancellationTokenSource();
   Task<int> t1 = new Task<int>(() =>
   {
       try
        {
          //while (!cts.IsCancellationRequested)
          {
             //cts.Token.ThrowIfCancellationRequested();
              Console.WriteLine($"t1 start 線程ID為{ Thread.CurrentThread.ManagedThreadId}");
              Thread.Sleep(1000);
               Console.WriteLine($"t1 end 線程ID為{ Thread.CurrentThread.ManagedThreadId}");
           }
            return 1;
         }
         catch (Exception ex)
         {
            Console.WriteLine("---Exception---");
            return 0;
         } 
   }, cts.Token);

   t1.Start();
   //  Console.WriteLine("---end---{0}", t1.Result);
    var t2 = Task.Run(() =>
    {
       Thread.Sleep(100);
       Console.WriteLine($"t2線程ID為{ Thread.CurrentThread.ManagedThreadId}");
    });
    Console.ReadLine();
  }
}

輸出為:

 如果打開注釋Console.WriteLine("---end---{0}", t1.Result);則任務阻塞,等待t1執行完,拿到返回值t1.Result之后才繼續執行,輸出為:

 5、任務的取消

任務出錯或用戶取消了操作,則需要用到任務的取消TaskCanceledException。

cts.Cancel()設置了cts.IsCancellationRequested為true,

然后cts.Token.ThrowIfCancellationRequested()這一句拋出了異常。

 執行結果為:

 如果主線程不延時或延時為10,則有可能在任務啟動之前就停止了任務,就不會發生任務拋出異常了,由於系統調度問題,一定情況下輸出與上面的一樣,大部分情況輸出為:

 此外,也可以在task中查詢標志。while (!cts.IsCancellationRequested),如果取消則不在執行。如果一次取消多個任務,可以使用CancellationTokenSource.CreateLinkedTokenSource實現。

幫助文檔:

https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.cancellationtokensource?view=netframework-4.8

從開始.NET Framework 4,.NET Framework 使用統一的模型來協作取消異步操作或長時間運行的同步操作涉及兩個對象:

一個CancellationTokenSource對象,它提供取消標記通過其Token屬性,並將取消消息通過調用其Cancel或CancelAfter方法。

一個CancellationToken對象,指示是否請求取消。

用於實現協作取消模型的常規模式是:

實例化 CancellationTokenSource 對象,此對象管理取消通知並將其發送給單個取消標記。

將 CancellationTokenSource.Token 屬性返回的標記傳遞給每個偵聽取消的任務或線程。

調用CancellationToken.IsCancellationRequested從接收取消標記的操作的方法。 提供有關每個任務或線程來響應取消請求的機制。 無論你選擇取消操作和完全操作方式,取決於你的應用程序邏輯。

調用 CancellationTokenSource.Cancel 方法以提供取消通知。 這將設置CancellationToken.IsCancellationRequested到取消標記的每個副本上的屬性true。

調用Dispose方法在您使用完CancellationTokenSource對象。

6、Task和Thread

Task是基於Thread的,是比較高層級的封裝,Task任務最終還是需要Thread來執行。

Task比Thread的開銷小,Task默認使用線程池。

Task默認使用后台線程來執行,Thread默認是前台線程。

Task使用參數和返回值都比Thread簡單。

Task的多任務調度比Thread靈活,Thead的join需要掛起調用者去執行被調用線程然后返回到主線程。而Task提供了多種Wait函數,可以等待其他線程執行。

7、參考文檔

本文只是學習筆記,水平有限,拋磚迎玉。

1、基於任務的異步編程

https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-programming/task-based-asynchronous-programming

2、Task 類

https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task?view=netframework-4.8

3、Difference Between Task and Thread 

https://www.dotnetforall.com/difference-task-and-thread/

 4、Task And Thread In C#

 https://www.c-sharpcorner.com/article/task-and-thread-in-c-sharp/

5、C#多線程和異步(一)——基本概念和使用方法

https://www.cnblogs.com/wyy1234/p/9166444.html

 


免責聲明!

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



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