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

Roy*_*mir 188 .net c# .net-4.0 task-parallel-library taskcompletionsource

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

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

我在这里看到这个例子:

如果我需要一种方法来异步执行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; 
}
Run Code Online (Sandbox Code Playgroud)

可以使用*如果我没有Task.Factory.StartNew- 但我确实Task.Factory.StartNew.

题:

可有人请举例相关的情景解释直接TaskCompletionSource 而不是一个假想中,我没有的情况 Task.Factory.StartNew

Gam*_*ing 220

当只有基于事件的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;
}
Run Code Online (Sandbox Code Playgroud)

因此,当与c#5 async关键字一起使用时,它特别有用.

  • 你能用语言写出我们在这里看到的是什么吗?就像在某个地方等待`SomeApiWrapper`,直到发布者提出导致此任务完成的事件? (4认同)
  • 只是一个更新,微软在NuGet上发布了`Microsoft.Bcl.Async`包,允许在.NET 4.0项目中使用`async/await`关键字(建议使用VS2012及更高版本). (4认同)
  • 这个实现的问题是这会产生内存泄漏,因为事件永远不会从 obj.Done 释放 (3认同)
  • 这个答案并没有想象中那么有帮助。它只是显示代码,没有任何真正的解释。我认为它还可以改进。 (2认同)

Eri*_*rik 75

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

我能想到的最有益的例子就是工作时Socket.它具有旧的APM和EAP模式,但不具备和拥有的awaitable Task方法.TcpListenerTcpClient

我个人在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;
    }
Run Code Online (Sandbox Code Playgroud)

我传递socketBeginAccept方法,使我获得稍许的性能提升出来的没有扯起本地参数的编译器.

然后美丽的一切:

 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();
Run Code Online (Sandbox Code Playgroud)

  • @Tola因为这会创建一个在线程池线程上运行的新任务,但上面的代码使用了由BeginAccept启动的i/o完成线程,它不会启动新线程. (22认同)
  • 谢谢,@ Frans-Bouma.那么TaskCompletionSource是一种将使用Begin ... End ...语句的代码转换成任务的方便方法吗? (4认同)
  • @TolaOdejayi迟到的回复,但是这是我找到的主要用例之一.它非常适合这种代码转换. (3认同)
  • 查看[TaskFactory &lt;TResult&gt; .FromAsync](https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.taskfactory-1.fromasync?view=netframework-4.0)进行包装“开始...结束...”语句。 (3认同)
  • 为什么 Task.Factory.StartNew 在这里不起作用? (2认同)

Adi*_*ter 37

对我来说,使用的经典场景TaskCompletionSource是,我的方法可能不一定需要耗费大量时间.它允许我们做的是选择我们想要使用新线程的特定情况.

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

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

  • 这实际上不是需要TCS的情况.你可以简单地使用`Task.FromResult`来做到这一点.当然,如果你使用的是4.0并且没有`Task.FromResult`,那么你使用TCS就是*编写你自己的*`FromResult`. (11认同)

Sar*_*rin 23

这篇博客文章中,Levi Botelho描述了如何使用TaskCompletionSource为Process编写异步包装器,以便您可以启动它并等待其终止.

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;
}
Run Code Online (Sandbox Code Playgroud)

及其用法

await RunProcessAsync("myexecutable.exe");
Run Code Online (Sandbox Code Playgroud)


sup*_*jos 13

看起来没有人提到,但我猜单元测试也可以被认为是真实的生活.

我发现TaskCompletionSource在使用异步方法模拟依赖项时非常有用.

在实际测试中的程序:

public interface IEntityFacade
{
  Task<Entity> GetByIdAsync(string id);
}
Run Code Online (Sandbox Code Playgroud)

在单元测试中:

// 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);
  // ...
}
Run Code Online (Sandbox Code Playgroud)

毕竟,TaskCompletionSource的这种用法似乎是"一个不执行代码的Task对象"的另一种情况.


v1p*_*p3r 12

TaskCompletionSource用于创建不执行代码的Task对象.在真实世界场景中,TaskCompletionSource是I/O绑定操作的理想选择.这样,您可以获得任务的所有好处(例如,返回值,延续等),而不会在操作期间阻塞线程.如果您的"函数"是IO绑定操作,则不建议使用新任务阻止线程.而是使用TaskCompletionSource,您可以创建一个从属任务,以指示您的I/O绑定操作何时完成或出现故障.


uri*_*rig 5

这篇文章中有一个真实世界的例子,来自"Parallel Programming with .NET"博客.你真的应该阅读它,但无论如何这里是一个总结.

博客文章显示了两个实现:

"用于创建"延迟"任务的工厂方法,在发生某些用户提供的超时之前,实际上不会安排这些任务."

所示的第一个实现基于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();
    });
}
Run Code Online (Sandbox Code Playgroud)

  • beucase你回到你离开的环境,其中Continuewith不会保留上下文.(不是默认情况下)如果action()中的下一个语句导致异常,则很难捕获它,使用await会将您显示为常规异常. (5认同)
  • 为什么不只是`await Task.Delay(millisecondsDelay); 行动(); return;`或(在.Net 4.0中)`return Task.Delay(millisecondsDelay).ContinueWith(_ => action());` (3认同)