【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);
}

 

posted @ 2022-02-09 11:19  小林野夫  阅读(5956)  评论(0编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/