TaskCompletionSource<T>被使用?

AFAIK,它所知道的只是在某些时候,它的SetResultSetException方法被调用来完成通过其Task属性暴露的Task<T>

换句话说,它充当Task<TResult>及其完成过程的生产者。

我看到这里<强> < / >强的例子:

如果我需要一种方法来异步执行Func<T>,并且有一个Task<T>

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.

问题:

谁能举例解释一个与直接TaskCompletionSource相关的场景吗 而不是假设情况,在这种情况下我没有Task.Factory.StartNew?< / p >
115793 次浏览

我主要在只有基于事件的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的一个经典场景是,我的方法可能不会一定必须执行耗时的操作。它允许我们做的是选择我们想要使用新线程的特定情况。

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

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

我使用TaskCompletionSource的真实场景是在实现下载队列时。在我的例子中,如果用户开始100次下载,我不想一次性将它们全部关闭,因此我返回一个附加到TaskCompletionSource的任务而不是返回一个分层任务。一旦下载完成,处理队列的线程就完成了任务。

这里的关键概念是,当客户端请求启动任务时,我正在将其与实际启动任务时分离。在这种情况下,因为我不希望客户端必须处理资源管理。

注意,你可以在。net 4中使用async/await,只要你使用的是c# 5编译器(VS 2012+),请参阅在这里了解更多细节。

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

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

我个人对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();

TaskCompletionSource用于创建不执行代码的任务对象。 在现实场景中,TaskCompletionSource是I/O约束操作的理想选择。通过这种方式,你可以获得任务的所有好处(例如,返回值,延续等),而不会在操作期间阻塞线程。如果你的“function”是一个I/O绑定操作,不建议使用新的任务阻塞线程。相反,使用TaskCompletionSource,你可以创建一个从任务来指示你的I/O绑定操作何时完成或发生错误

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

这篇博文展示了两种实现:

”一个用于创建“延迟”任务的工厂方法,这些任务不会延迟 实际上被调度,直到发生用户提供的超时。" < / p >

第一个实现基于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();
});
}

这篇博文中,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");

这可能是过于简化的事情,但TaskCompletion源代码允许等待一个事件。从tc开始。SetResult只在事件发生时设置,调用者可以等待任务。

观看这段视频了解更多:

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

似乎没有人提到,但我想单元测试也可以被认为是现实生活足够。

我发现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的这种用法似乎是“不执行代码的任务对象”的另一种情况。

我已经使用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.
}

Blazor的WebAssemblyHost也使用这个来防止. net虚拟机停止。

await new TaskCompletionSource().Task;