什么时候应该使用TaskCompletionSource <T>?
什么时候应该使用TaskCompletionSource?
AFAIK,所有它知道的是,在某个时候,它的SetResult
或SetException
方法正在被调用来完成通过其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模式,但不是TcpListener
和TcpClient
具有的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获取更多见解:
- 将CheckForIllegalCrossThreadCalls设置为false以避免debugging期间出现跨线程错误,是否安全?
- C#相当于Java的<? 在generics中扩展Base>
- 如何使用WPF和.NET 3.5注册一个全局热键来说CTRL + SHIFT +(LETTER)?
- 用Nant构build.NET 4项目
- 在.Net中调用Web服务时,绕过无效的SSL证书错误
- Visual Studio 2015中的共享项目和类库有什么区别?
- 你如何防止IDisposable传播到所有的类?
- C#为什么等号可能产生不相等的哈希值?
- 我怎样才能在一个DataGridView列中alignment文本?