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

时间:2013-03-09 22:24:41

标签: c# .net .net-4.0 task-parallel-library taskcompletionsource

AFAIK,它所知道的是,在某些时候,正在调用其SetResultSetException方法来完成通过其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假设的情况?

11 个答案:

答案 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 TaskTcpListener具有的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仅在事件发生时设置,因此调用者可以等待该任务。

观看此视频以获取更多见解:

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

答案 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