【问题标题】:TaskCompletionSource - Trying to understand threadless async workTaskCompletionSource - 试图理解无线程异步工作
【发布时间】:2020-04-09 14:52:42
【问题描述】:

我试图了解TaskCompletionSource 的用途及其与异步/无线程工作的关系。我想我有一个大致的想法,但我想确保我的理解是正确的。

我首先开始研究任务并行库 (TPL),以了解是否有一种创建自己的无线程/异步工作的好方法(例如,您正在尝试提高 ASP.NET 站点的可扩展性)以及了解的 TPL 看起来它将在未来非常重要(async/await)。这让我找到了TaskCompletionSource

根据我的理解,将TaskCompletionSource 添加到您的一个类中并没有真正起到使您的编码异步的作用;如果您仍在执行同步代码,那么对您的代码的调用将被阻止。我认为微软 API 也是如此。例如,在WebClient 类之外的DownloadStringTaskAsync 中,他们最初执行的任何设置/同步代码都会阻塞。您正在执行的代码必须在某个线程上运行,要么是当前线程,要么您将不得不派生一个新线程。

因此,当您从 Microsoft 调用其他 async 调用时,您可以在自己的代码中使用 TaskCompletionSource,这样您的类的客户端就不必为您的类创建新线程以免阻塞。

不确定 Microsoft 如何在内部执行其异步 API。例如,对于 .Net 4.5,SqlDataReader 有一个新的 async 方法。我知道有 IO 完成端口。我认为这是大多数 C# 开发人员可能不会使用的较低级别的抽象(C++?)。不确定 IO 完成端口是否适用于数据库或网络调用 (HTTP),或者它是否仅用于文件 IO。

所以问题是,我的理解是否正确?是不是有些东西我表述不正确?

【问题讨论】:

  • 如果我的理解是正确的......不确定是不是

标签: asynchronous task-parallel-library


【解决方案1】:

TaskCompletionSource 用于创建不执行代码的Task 对象。

Microsoft 的新异步 API 大量使用它们 - 任何时候存在基于 I/O 的异步操作(或其他非基于 CPU 的异步操作,如超时)。此外,您编写的任何async Task 方法都将使用TCS 来完成其返回的Task

我有一篇博文 Creating Tasks,其中讨论了创建 Task 实例的不同方法。它是从 async/await 角度(不是 TPL 角度)编写的,但在这里仍然适用。

另请参阅 Stephen Toub 的优秀帖子:

【讨论】:

  • 谢谢斯蒂芬,但我对内部的理解正确吗?
  • 任何异步方法都(当然)有一个同步部分用于启动异步操作。 IIRC,IOCP 可用于任何类型的基于 HANDLE 的数据传输,并且(今天)将 IOCP 操作包裹在 Begin/End 包裹在包裹它的 async 方法中并不少见在Task 中(使用TaskCompletionSource)。
  • 我也在尝试了解 TaskCompletionSource 的用法,我有点困惑为什么你会有一个不执行代码的任务。我读了你的博文;似乎 TCS 被用作调用者可以订阅的事件,但是当您可以使用常规任务并等待任务完成时,为什么要这样做呢?
  • TaskCompletionSource 在逻辑上更像是一个事件处理程序,并且产生一个Task,其他代码可以await。有两种任务:Delegate Tasks 和 Promise Tasks;我有a blog post that explains some of the differences
【解决方案2】:

我喜欢http://tutorials.csharp-online.net/TaskCompletionSource中提供的解释

(对不起,链接可能暂时失效了)

前两段如下

我们已经了解了 Task.Run 如何创建一个任务,该任务在 池化(或非池化)线程。创建任务的另一种方法是使用 任务完成源。

TaskCompletionSource 让您可以使用任何操作创建任务 一段时间后开始和结束。它通过给你一个“奴隶”来工作 您手动驱动的任务——通过指示操作何时完成 或故障。这是 I/O 密集型工作的理想选择:您将获得所有好处 任务(具有传播返回值、异常、 和延续)在持续时间内不阻塞线程 操作。

要使用 TaskCompletionSource,您只需实例化该类。它 公开一个 Task 属性,该属性返回一个您可以等待的任务 并附加延续——就像任何其他任务一样。任务, 但是,完全由 TaskCompletionSource 对象通过 以下方法:

public class TaskCompletionSource<TResult> 
{ 
 public void SetResult(TResult result); 
 public void SetException (Exception exception); 

 public void SetCanceled();   
 public bool TrySetResult (TResult result); 
 public bool TrySetException (Exception exception); 
 public bool TrySetCanceled();
 ... 
}

调用这些方法中的任何一个都会发出任务信号,将其放入 完成、故障或取消状态(我们将在 “取消”部分)。你应该调用这些方法之一 恰好一次:如果再次调用,SetResult、SetException 或 SetCanceled 将抛出异常,而 Try* 方法返回 false。

以下示例在等待 5 秒后打印 42:

var tcs = new TaskCompletionSource<int>();
new Thread (() =>     {
                       Thread.Sleep (5000); 
                       tcs.SetResult (42); 
                      })    
           .Start();   
Task<int> task = tcs.Task;    // Our "slave" task. 
Console.WriteLine(task.Result);  // 42

其他有趣的名言

TaskCompletionSource 的真正威力在于创建不 捆绑线程。

.. 以后

我们在没有线程的情况下使用TaskCompletionSource意味着一个线程 仅在五秒钟后继续开始时才启用。我们 可以通过一次启动 10,000 个这样的操作来证明这一点 没有错误或过多的资源消耗:

【讨论】:

    猜你喜欢
    • 2016-01-16
    • 2017-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多