AFAIK,它所知道的是,在某些时候,正在调用其SetResult
或SetException
方法来完成通过其Task<T>
属性公开的Task
。
换句话说,它充当Task<TResult>
及其完成的生产者。
我看到here示例:
如果我需要一种异步执行Func并具有Task的方法 代表该行动。
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
。
问题:
有人可以通过示例向<{1}}解释与直接相关的方案
而不是我没有TaskCompletionSource
的假设的情况?
答案 0 :(得分:211)
当只有基于事件的api可用时,我主要使用它(for example windows phone 8 sockets):
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
关键字一起使用时,它尤其有用。
答案 1 :(得分:72)
根据我的经验,TaskCompletionSource
非常适合将旧的异步模式包装到现代async/await
模式中。
我能想到的最有用的例子是使用Socket
时。它具有旧的APM和EAP模式,但不是awaitable Task
和TcpListener
具有的TcpClient
方法。
我个人对NetworkStream
课有几个问题,而更喜欢原始Socket
。由于我也喜欢async/await
模式,因此我制作了一个扩展类SocketExtender
,为Socket
创建了几种扩展方法。
所有这些方法都使用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;
}
我将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();
答案 2 :(得分:34)
对我而言,使用TaskCompletionSource
的经典方案是,我的方法可能不会必然进行耗时的操作。它允许我们做的是选择我们想要使用新线程的特定情况。
这是一个很好的例子,当你使用缓存时。您可以使用GetResourceAsync
方法,在缓存中查找所请求的资源,并在找到资源时立即返回(不使用新线程,使用TaskCompletionSource
)。只有在找不到资源时,我们才会使用新线程并使用Task.Run()
检索它。
可以在此处看到代码示例:How to conditionally run a code asynchonously using tasks
答案 3 :(得分:23)
在this blog post中,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;
}
及其用法
await RunProcessAsync("myexecutable.exe");
答案 4 :(得分:11)
TaskCompletionSource 用于创建不执行代码的 Task 对象。 在真实世界场景中, TaskCompletionSource 非常适合I / O绑定操作。这样,您可以获得任务的所有好处(例如,返回值,延续等),而不会在操作期间阻塞线程。如果你的&#34;功能&#34;是一个IO绑定操作,建议不要使用新的任务来阻止线程。而是使用 TaskCompletionSource ,您可以创建一个从属任务,以指示您的I / O绑定操作何时完成或出现故障。
答案 5 :(得分:10)
看起来没人提到,但我猜单元测试也可以被视为现实生活。
在使用异步方法模拟依赖项时,我发现TaskCompletionSource
非常有用。
在实际测试中的程序:
public interface IEntityFacade
{
Task<Entity> GetByIdAsync(string id);
}
在单元测试中:
// 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的这种用法似乎是另一种情况,即#34;一个不执行代码的Task对象&#34;。
答案 6 :(得分:5)
在这个post from the "Parallel Programming with .NET" blog中有一个真实的例子,有一个不错的解释。你真的应该阅读它,但无论如何这里是一个总结。
博客文章显示了两个实现:
“创建”延迟“任务的工厂方法,不会创建”延迟“任务的工厂方法 实际上会被安排,直到发生一些用户提供的超时。“
显示的第一个实现基于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();
});
}
答案 7 :(得分:3)
我使用TaskCompletionSource
的真实世界场景是在实现下载队列时。在我的情况下,如果用户开始下载100次,我不想立刻将它们全部关闭,因此我返回一个附加到TaskCompletionSource
的任务,而不是返回一个strated任务。下载完成后,正在运行队列的线程完成任务。
这里的关键概念是,当客户端要求从实际启动时启动任务时,我正在解耦。在这种情况下,因为我不希望客户端必须处理资源管理。
请注意,只要您使用的是C#5编译器(VS 2012+),就可以在.net 4中使用async / await,有关详细信息,请参阅here。
答案 8 :(得分:3)
这可能过于简单了,但TaskCompletion源允许人们等待事件。由于tcs.SetResult仅在事件发生时设置,因此调用者可以等待该任务。
观看此视频以获取更多见解:
答案 9 :(得分:0)
我已经使用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.
}
答案 10 :(得分:-1)
TaskCompletionSource
对任务而言,WaitHandle
对线程而言。因此,我们可以使用TaskCompletionSource
执行精确的信号传输。
一个例子就是我对这个问题的回答:ContentDialog delay after OK clicked