C# 什么时候应该使用 TaskCompletionSource<T>?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15316613/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
When should TaskCompletionSource<T> be used?
提问by Royi Namir
AFAIK, all it knows is that at some point, its SetResult
or SetException
method is being called to complete the Task<T>
exposed through its Task
property.
AFAIK,它只知道在某个时候,它的SetResult
orSetException
方法被调用以Task<T>
通过其Task
属性完成公开。
In other words, it acts as the producer for a Task<TResult>
and its completion.
换句话说,它充当 aTask<TResult>
及其完成的生产者。
I saw herethe example:
If I need a way to execute a Func asynchronously and have a Task to represent that operation.
如果我需要一种方法来异步执行 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;
}
Which could be used *if I didn't have Task.Factory.StartNew
-
But I dohave Task.Factory.StartNew
.
可以使用 * 如果我没有Task.Factory.StartNew
- 但我确实有Task.Factory.StartNew
.
Question:
题:
Can someone please explain by example a scenario related directlyto TaskCompletionSource
and not to a hypotheticalsituation in which I don't have Task.Factory.StartNew
?
可有人请举例相关的情景解释直接到TaskCompletionSource
而不是一个假想中,我没有的情况 Task.Factory.StartNew
?
采纳答案by GameScripting
I mostly use it when only an event based API is available (for example Windows Phone 8 sockets):
我主要在只有基于事件的 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;
}
So it's especially useful when used together with the C#5 async
keyword.
因此,当与 C#5async
关键字一起使用时,它特别有用。
回答by Adi Lester
To me, a classic scenario for using TaskCompletionSource
is when it's possible that my method won't necessarilyhave to do a time consuming operation. What it allows us to do is to choose the specific cases where we'd like to use a new thread.
对我来说,一个经典的使用场景TaskCompletionSource
是我的方法不一定需要执行耗时的操作。它允许我们做的是选择我们想要使用新线程的特定情况。
A good example for this is when you use a cache. You can have a GetResourceAsync
method, which looks in the cache for the requested resource and returns at once (without using a new thread, by using TaskCompletionSource
) if the resource was found. Only if the resource wasn't found, we'd like to use a new thread and retrieve it using Task.Run()
.
一个很好的例子是当您使用缓存时。您可以有一个GetResourceAsync
方法,它在缓存中查找所请求的资源,并在找到资源时立即返回(不使用新线程,通过使用TaskCompletionSource
)。仅当未找到资源时,我们才想使用新线程并使用Task.Run()
.
A code example can be seen here: How to conditionally run a code asynchonously using tasks
可以在此处查看代码示例:如何使用任务有条件地异步运行代码
回答by Yaur
I real world scenario where I have used TaskCompletionSource
is when implementing a download queue. In my case if the user starts 100 downloads I don't want to fire them all off at once and so instead of returning a strated task I return a task attached to TaskCompletionSource
. Once the download gets completed the thread that is working the queue completes the task.
我使用的真实世界场景TaskCompletionSource
是在实现下载队列时。在我的情况下,如果用户开始 100 次下载,我不想一次将它们全部关闭,因此我不会返回一个计划任务,而是返回一个附加到TaskCompletionSource
. 一旦下载完成,队列中的线程就会完成任务。
The key concept here is that I am decoupling when a client asks for a task to be started from when it actually gets started. In this case because I don't want the client to have to deal with resource management.
这里的关键概念是,当客户端要求任务从实际开始时开始时,我正在解耦。在这种情况下,因为我不希望客户端必须处理资源管理。
note that you can use async/await in .net 4 as long as you are using a C# 5 compiler (VS 2012+) see herefor more details.
请注意,您可以使用异步/地等待着.NET 4,只要您使用的是C#编译器5(VS 2012+)看这里了解更多详情。
回答by Erik
In my experiences, TaskCompletionSource
is great for wrapping old asynchronous patterns to the modern async/await
pattern.
根据我的经验,TaskCompletionSource
非常适合将旧的异步模式包装到现代async/await
模式中。
The most beneficial example I can think of is when working with Socket
. It has the old APM and EAP patterns, but not the awaitable Task
methods that TcpListener
and TcpClient
have.
我能想到的最有益的例子是使用Socket
. 它具有旧的 APM 和 EAP 模式,但没有和拥有的awaitable Task
方法。TcpListener
TcpClient
I personally have several issues with the NetworkStream
class and prefer the raw Socket
. Being that I also love the async/await
pattern, I made an extension class SocketExtender
which creates several extension methods for Socket
.
我个人对这NetworkStream
门课有几个问题,更喜欢 raw Socket
. 由于我也喜欢这种async/await
模式,我SocketExtender
创建了一个扩展类,它为Socket
.
All of these methods make use of TaskCompletionSource<T>
to wrap the asynchronous calls like so:
所有这些方法都TaskCompletionSource<T>
用来包装异步调用,如下所示:
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;
}
I pass the socket
into the BeginAccept
methods so that I get a slight performance boost out of the compiler not having to hoist the local parameter.
我传递socket
到BeginAccept
方法,使我获得稍许的性能提升出来的没有扯起本地参数的编译器。
Then the beauty of it all:
然后是这一切的美丽:
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();
回答by v1p3r
TaskCompletionSourceis used to create Taskobjects that don't execute code. In real world scenarios, TaskCompletionSourceis ideal for I/O bound operations. This way, you get all the benefits of tasks (e.g. return values, continuations, etc) without blocking a thread for the duration of the operation. If your "function" is an I/O bound operation, it isn't recommended to block a thread using a new Task. Instead, using TaskCompletionSource, you can create a slave task to just indicate when your I/O bound operation finishes or faults.
TaskCompletionSource用于创建不执行代码的Task对象。在实际场景中,TaskCompletionSource是 I/O 绑定操作的理想选择。这样,您可以获得任务的所有好处(例如返回值、延续等),而不会在操作期间阻塞线程。如果您的“函数”是 I/O 绑定操作,则不建议使用新的Task阻塞线程。相反,使用TaskCompletionSource,您可以创建一个从属任务来指示您的 I/O 绑定操作何时完成或出现故障。
回答by urig
There's a real world example with a decent explanation in this post from the "Parallel Programming with .NET" blog. You really should read it, but here's a summary anyway.
在这篇来自“Parallel Programming with .NET”博客的文章中,有一个真实世界的例子,有一个很好的解释。你真的应该阅读它,但无论如何这里有一个摘要。
The blog post shows two implementations for:
博文展示了以下两种实现:
"a factory method for creating “delayed” tasks, ones that won't actually be scheduled until some user-supplied timeout has occurred."
“一种用于创建“延迟”任务的工厂方法,这些任务在用户提供的超时发生之前实际上不会被安排。”
The first implementation shown is based on Task<>
and has two major flaws. The second implementation post goes on to mitigate these by using TaskCompletionSource<>
.
显示的第一个实现基于Task<>
并有两个主要缺陷。第二个实现帖子继续通过使用TaskCompletionSource<>
.
Here's that second implementation:
这是第二个实现:
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();
});
}
回答by Sarin
In this blog post, Levi Botelho describes how to use the TaskCompletionSource
to write an asynchronous wrapper for a Process such that you can launch it and await its termination.
在这篇博文中,Levi Botelho 描述了如何使用TaskCompletionSource
为进程编写异步包装器,以便您可以启动它并等待其终止。
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;
}
and its usage
及其用法
await RunProcessAsync("myexecutable.exe");
回答by nmishr
This may be oversimplifying things but the TaskCompletion source allows one to await on an event. Since the tcs.SetResult is only set once the event occurs, the caller can await on the task.
这可能过于简单化了,但 TaskCompletion 源允许人们等待事件。由于 tcs.SetResult 仅在事件发生时设置,因此调用者可以等待任务。
Watch this video for more insights:
观看此视频以获取更多见解:
回答by superjos
It looks like no one mentioned, but I guess unit tests too can be considered real lifeenough.
看起来没有人提到过,但我想单元测试也可以被视为现实生活。
I find TaskCompletionSource
to be useful when mocking a dependency with an async method.
我发现TaskCompletionSource
在使用异步方法模拟依赖项时很有用。
In actual program under test:
在实际测试程序中:
public interface IEntityFacade
{
Task<Entity> GetByIdAsync(string id);
}
In unit tests:
在单元测试中:
// 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);
// ...
}
After all, this usage of TaskCompletionSource seems another case of "a Task object that does not execute code".
毕竟,TaskCompletionSource 的这种用法似乎是“不执行代码的 Task 对象”的另一种情况。
回答by Johan Gov
I've used TaskCompletionSource
to run a Task until it is cancelled. In this case it's a ServiceBus subscriber that I normally want to run for as long as the application runs.
我曾经TaskCompletionSource
运行一个任务,直到它被取消。在这种情况下,它是一个 ServiceBus 订阅者,我通常希望在应用程序运行时一直运行它。
public async Task RunUntilCancellation(
CancellationToken cancellationToken,
Func<Task> onCancel)
{
var doneReceiving = new TaskCompletionSource<bool>();
cancellationToken.Register(
async () =>
{
await onCancel();
doneReceiving.SetResult(true); // Signal to quit message listener
});
await doneReceiving.Task.ConfigureAwait(false); // Listen until quit signal is received.
}