36

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

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

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

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

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

所以问题是,我的理解正确吗?是否有某些事情我代表不正确?

4

2 回答 2

61

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

微软的新异步 API 大量使用了它们——只要有基于 I/O 的异步操作(或其他非基于 CPU 的异步操作,如超时)。此外,async Task您编写的任何方法都将使用 TCS 来完成其返回的Task.

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

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

于 2012-05-02T19:06:55.710 回答
8

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

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

前两段如下

我们已经看到了 Task.Run 如何创建一个在池(或非池)线程上运行委托的任务。创建任务的另一种方法是使用 TaskCompletionSource。

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 意味着只有在 continuation 开始时才使用线程,即 5 秒后。我们可以通过一次启动 10,000 个这样的操作来证明这一点,而不会出现错误或过多的资源消耗:

于 2014-04-09T14:25:22.397 回答