【C# Task】TaskCompletionSource


TaskCompletionSource具體功能

用於封裝一個沒有不帶委托的任務實列。可以在其他線程控制該任務實列什么時候結束、取消、錯誤。類似於EventWaitHandle的功能。

屬性

Task

方法

TaskCompletionSource應用

TaskCompletionSource :表示未綁定委托的 Task 的制造者方,並通過 Task 屬性提供對使用者方的訪問。

由TaskCompletionSource創建的任務的狀態是由TaskCompletionSource上的方法顯式控制的。

TaskCompletionSource的所有成員都是線程安全的,可以在多個線程中並發使用。

https://blog.csdn.net/Czhenya/article/details/120442315

 

1、 使用 TaskCompletionSource 實現暫停功能

案例:火車票選購>選好車票>支付(軟件界面暫停)>跳到支付頁面>支付成功( 支付頁面繼續執行)>跳到支付頁面

TaskCompletionSource<bool> tcs = new();
Task.Run(async () => await ChoiceTicket(tcs));
Task.Run(async () => await PayForMoney(tcs));

Console.Read();
 //選擇車票
static async  Task<bool> ChoiceTicket(TaskCompletionSource<bool> tcs)
{

  Console.WriteLine("選購火車票") ;
  Console.WriteLine("跳轉到到支付頁面");

    await tcs.Task;
    Console.WriteLine("訂票完成");
  return tcs.Task.Result;
}

//選擇支付頁面
static async Task PayForMoney(TaskCompletionSource<bool> tcs)
{

  await  Task.Delay(3000).ContinueWith((t) => Console.WriteLine("選購支付寶支付"));

    tcs.SetResult(true);
    Console.WriteLine("支付完成");
    Console.WriteLine("跳到火車票軟件完成");
}

 通過控制台可以看到先輸出 A 開始 然后等待一秒輸出 B 開始 之后調用了 SetResult 方法,然后輸出 A 完成 也就是 A 方法在 await taskCompletionSource.Task 等待直到 B 調用 taskCompletionSource.SetResult(true) 方法才繼續往下

 

2、將回調(事件)封裝為任務完成源

作用:使得代碼更容易閱讀和維護

事件完成后調用回調方法

在之前的指南中,我們討論了生成圖像的應用程序;假設您需要應用程序將這些圖像上傳到某個地方。假設有一個名為"MyBox"的雲文件存儲服務,它有一個.NET庫,上傳文件的庫方法如下:

public static void UploadFile(string name, byte[] data, Action<bool> onCompleted);
查閱庫的文檔后,您會發現 onCompleted 是在上載完成時調用的回調,如果上載成功,則值為 true,如果上載失敗,則值為 false。若要使用此庫方法上載文件,必須執行如下操作,前提是您的應用程序具有可顯示名為 statusText 的文本的內容:
public async void OnUploadButtonClicked()
{
  statusText.Text = "Generating Image...";
  byte[] imageData = await GenerateImage();
  statusText.Text = "Uploading Image...";  
  MyBox.UploadFile("image.jpg", imageData, success => 
  {
    statusText.Text = success ? string.Empty : "Error Uploading";
  });
}

 

修改成TaskCompletionSource


public
static Task<bool> UploadFile(string name, byte[] data) { var taskCompletionSource = new TaskCompletionSource<bool>(); try { MyBox.UploadFile(name, data, success => { taskCompletionSource.SetResult(success); }); } catch (Exception ex) { taskCompletionSource.SetException(ex); } return taskCompletionSource.Task; }
public async void OnUploadButtonClicked()
{
  statusText.Text = "Generating Image...";
  byte[] imageData = await GenerateImage();
  statusText.Text = "Uploading Image...";  
  bool success = await MyBoxHelper.UploadFile("image.jpg", imageData);
  statusText.Text = success ? string.Empty : "Error Uploading";
}

像同步一樣寫代碼。簡直不要太爽。

 創建TaskCompletionSource時建議使用TaskCreationOptions.RunContinuationsAsynchronously屬性

對於編寫類庫的人來說TaskCompletionSource<T>是一個具有非常重要的作用,默認情況下任務延續可能會在調用try/set(Result/Exception/Cancel)的線程上進行運行,這也就是說作為編寫類庫的人來說必須需要考慮上下文,這通常是非常危險,可能就會導致死鎖線程池飢餓 *數據結構損壞(如果代碼異常運行)

所以在創建TaskCompletionSourece<T>時,應該使用TaskCreationOption.RunContinuationAsyncchronously參數將后續任務交給線程池進行處理

❌下面例子就沒有使用TaskCreationOptions.RunComtinuationsAsynchronously,

static void Main(string[] args)
{
     ThreadPool.SetMinThreads(100, 100);
     Console.WriteLine("Main CurrentManagedThreadId:" + Environment.CurrentManagedThreadId);
     var tcs = new TaskCompletionSource<bool>();
     //  使用TaskContinuationOptions.ExecuteSynchronously來測試延續任務
     ContinueWith(1, tcs.Task);
     //  測試await延續任務
     ContinueAsync(2, tcs.Task);
     Task.Run(() =>
     {
        Console.WriteLine("Task Run CurrentManagedThreadId:" + Environment.CurrentManagedThreadId );
        tcs.TrySetResult(true);
     });
     Console.ReadLine();
}
static void print(int id) => Console.WriteLine($"continuation:{id}\tCurrentManagedThread:{Environment.CurrentManagedThreadId}");
static async Task ContinueAsync(int id, Task task)
{
     await task.ConfigureAwait(false);
     print(id);
}
static Task ContinueWith(int id, Task task)
{
     return task.ContinueWith(
          t => print(id),
          CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}

 ☑️所以應該改為使用TaskCreationOptions.RunComtinuationsAsynchronously參數進行設置TaskCompletionSoure

static void Main(string[] args)
{
     ThreadPool.SetMinThreads(100, 100);
     Console.WriteLine("Main CurrentManagedThreadId:" + Environment.CurrentManagedThreadId);
     var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
     //  使用TaskContinuationOptions.ExecuteSynchronously來測試延續任務
     ContinueWith(1, tcs.Task);
     //  測試await延續任務
     ContinueAsync(2, tcs.Task);
     Task.Run(() =>
     {
        Console.WriteLine("Task Run CurrentManagedThreadId:" + Environment.CurrentManagedThreadId);
        tcs.TrySetResult(true);
     });
     Console.ReadLine();
}
static void print(int id) => Console.WriteLine($"continuation:{id}\tCurrentManagedThread:{Environment.CurrentManagedThreadId}");
static async Task ContinueAsync(int id, Task task)
{
     await task.ConfigureAwait(false);
     print(id);
}
static Task ContinueWith(int id, Task task)
{
     return task.ContinueWith(
          t => print(id),
          CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}

 


免責聲明!

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



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