什么时候应该使用TaskCompletionSource <T>?

什么时候应该使用TaskCompletionSource?

AFAIK,所有它知道的是,在某个时候,它的SetResultSetException方法正在被调用来完成通过其Task属性公开的Task<T>

换句话说,它作为一个Task<TResult>及其完成的生产者。

我在这里看到这个例子:

如果我需要一种方法来asynchronous执行一个Func,并有一个任务来表示该操作。

 public static Task<T> RunAsync<T>(Func<T> function) { if (function == null) throw new ArgumentNullException(“function”); var tcs = new TaskCompletionSource<T>(); ThreadPool.QueueUserWorkItem(_ => { try { T result = function(); tcs.SetResult(result); } catch(Exception exc) { tcs.SetException(exc); } }); return tcs.Task; } 

如果我没有Task.Factory.StartNew ,可以使用Task.Factory.StartNew

但我有Task.Factory.StartNew

有人可以请示例解释与TaskCompletionSource 直接相关的情况,而不是一个假设的情况下,我没有Task.Factory.StartNew

我主要使用它时,只有基于事件的api可用( 例如Windows Phone 8套接字 ):

 public Task<Args> SomeApiWrapper() { TaskCompletionSource<Args> tcs = new TaskCompletionSource<Args>(); var obj = new SomeApi(); // will get raised, when the work is done obj.Done += (args) => { // this will notify the caller // of the SomeApiWrapper that // the task just completed tcs.SetResult(args); } // start the work obj.Do(); return tcs.Task; } 

所以当与c#5 async关键字一起使用时尤其有用。

根据我的经验, TaskCompletionSource非常适合将旧的asynchronous模式包装到现代async/await模式。

我能想到的最有利的例子是使用Socket 。 它具有旧的APM和EAP模式,但不是TcpListenerTcpClient具有的awaitable Task方法。

我个人有NetworkStream类的几个问题,并喜欢原始的Socket 。 因为我也喜欢async/await模式,所以我做了一个扩展类SocketExtender ,它为Socket创build了几个扩展方法。

所有这些方法都使用TaskCompletionSource<T>来包装asynchronous调用,如下所示:

  public static Task<Socket> AcceptAsync(this Socket socket) { if (socket == null) throw new ArgumentNullException("socket"); var tcs = new TaskCompletionSource<Socket>(); socket.BeginAccept(asyncResult => { try { var s = asyncResult.AsyncState as Socket; var client = s.EndAccept(asyncResult); tcs.SetResult(client); } catch (Exception ex) { tcs.SetException(ex); } }, socket); return tcs.Task; } 

我将socket传递给BeginAccept方法,以便在编译器中轻微地提升性能,而不必提升本地参数。

那么这一切的美丽:

  var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listener.Bind(new IPEndPoint(IPAddress.Loopback, 2610)); listener.Listen(10); var client = await listener.AcceptAsync(); 

对我来说,使用TaskCompletionSource的经典场景是,当我的方法可能不一定需要耗费时间的操作时。 它允许我们做的是select我们想要使用新线程的特定情况。

一个很好的例子就是当你使用一个caching。 您可以有一个GetResourceAsync方法,该方法在caching中查找所请求的资源,并在find该资源时立即返回(不使用新线程,通过使用TaskCompletionSource )。 只有当资源没有find时,我们才想使用一个新的线程,并使用Task.Run()来检索它。

一个代码示例可以在这里看到: 如何有条件地使用任务asynchronous运行代码

TaskCompletionSource用于创build不执行代码的Task对象。 在真实世界的情况下TaskCompletionSource是I / O绑定操作的理想select。 通过这种方式,您可以获得任务的所有好处(例如返回值,延续等),而不会在操作期间阻塞线程。 如果你的“函数”是一个IO绑定操作,那么不build议使用一个新的Task来阻塞一个线程。 相反,使用TaskCompletionSource,您可以创build一个从属任务来指示您的I / O绑定操作何时完成或发生故障。

在这篇博客文章中 ,Levi Botelho介绍了如何使用TaskCompletionSource为Process编写一个asynchronous封装器,以便您可以启动它并等待其终止。

 public static Task RunProcessAsync(string processPath) { var tcs = new TaskCompletionSource<object>(); var process = new Process { EnableRaisingEvents = true, StartInfo = new ProcessStartInfo(processPath) { RedirectStandardError = true, UseShellExecute = false } }; process.Exited += (sender, args) => { if (process.ExitCode != 0) { var errorMessage = process.StandardError.ReadToEnd(); tcs.SetException(new InvalidOperationException("The process did not exit correctly. " + "The corresponding error message was: " + errorMessage)); } else { tcs.SetResult(null); } process.Dispose(); }; process.Start(); return tcs.Task; } 

及其用法

 await RunProcessAsync("myexecutable.exe"); 

看起来没有人提到,但我想unit testing也可以被认为是真实的生活

我发现TaskCompletionSource在用asynchronous方法TaskCompletionSource依赖时很有用。

在实际的被测程序中:

 public interface IEntityFacade { Task<Entity> GetByIdAsync(string id); } 

在unit testing中:

 // set up mock dependency (here with NSubstitute) TaskCompletionSource<Entity> queryTaskDriver = new TaskCompletionSource<Entity>(); IEntityFacade entityFacade = Substitute.For<IEntityFacade>(); entityFacade.GetByIdAsync(Arg.Any<string>()).Returns(queryTaskDriver.Task); // later on, in the "Act" phase private void When_Task_Completes_Successfully() { queryTaskDriver.SetResult(someExpectedEntity); // ... } private void When_Task_Gives_Error() { queryTaskDriver.SetException(someExpectedException); // ... } 

毕竟,TaskCompletionSource的这种用法似乎是“不执行代码的任务对象”的另一种情况。

这篇文章中有一个真实世界的例子, 在“.NET并行编程”博客中有一个很好的解释。 你真的应该读它,但是这里总结一下。

博客文章展示了两个实现:

“用于创build”延迟“任务的工厂方法,在实际发生用户提供的超时之前不会被调度。

显示的第一个实现是基于Task<> ,有两个主要缺陷。 第二个实现文章继续通过使用TaskCompletionSource<>来缓解这些问题。

这是第二个实现:

 public static Task StartNewDelayed(int millisecondsDelay, Action action) { // Validate arguments if (millisecondsDelay < 0) throw new ArgumentOutOfRangeException("millisecondsDelay"); if (action == null) throw new ArgumentNullException("action"); // Create a trigger used to start the task var tcs = new TaskCompletionSource<object>(); // Start a timer that will trigger it var timer = new Timer( _ => tcs.SetResult(null), null, millisecondsDelay, Timeout.Infinite); // Create and return a task that will be scheduled when the trigger fires. return tcs.Task.ContinueWith(_ => { timer.Dispose(); action(); }); } 

我使用TaskCompletionSource实际场景是在实现下载队列时。 在我的情况下,如果用户启动100次下载,我不想立即解雇他们,所以而不是返回一个固定的任务,我返回任务附加到TaskCompletionSource 。 下载完成后,正在工作队列的线程完成任务。

这里的关键概念是,当客户要求一个任务从实际开始的时候开始,我就解耦了。 在这种情况下,我不希望客户端必须处理资源pipe理。

请注意,只要您使用C#5编译器(VS 2012+),就可以在.net 4中使用async / await了解更多详细信息。

这可能是过度简化的事情,但TaskCompletion来源允许一个人等待一个事件。 由于事件发生后才设置tcs.SetResult,调用者可以等待任务。

观看此video获取更多见解:

http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Lucian03-TipsForAsyncThreadsAndDatabinding