代码之家  ›  专栏  ›  技术社区  ›  Royi Namir

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

  •  250
  • Royi Namir  · 技术社区  · 13 年前

    AFAIK,它所知道的是,在某个时候 SetResult SetException 正在调用方法以完成 Task<T> 通过其暴露 Task 所有物

    换句话说,它充当了 Task<TResult> 及其完成。

    我看到了 here 示例:

    如果我需要一种方法来执行 Func<T> 异步,并具有 任务<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 - 但我 任务工厂开始新建 .

    问题:

    有人能举例说明一个相关的场景吗 直接地 TaskCompletionSource 而不是 假设的 我没有的情况 任务工厂开始新建 ?

    11 回复  |  直到 5 年前
        1
  •  277
  •   Pang Ajmal PraveeN    6 年前

    我通常在只有基于事件的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 关键字。

        2
  •  92
  •   Erik    12 年前

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

    我能想到的最有益的例子是与 Socket 。它有旧的APM和EAP模式,但没有 awaitable Task 方法 TcpListener TcpClient

    我个人对 NetworkStream 等级,喜欢生的 插座 。因为我也喜欢 异步/等待 模式,我做了一个扩展类 SocketExtender 它为创建了几个扩展方法 插座 .

    所有这些方法都利用了 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();
    
        3
  •  41
  •   Community Mohan Dere    9 年前

    对我来说,这是一个经典的使用场景 TaskCompletionSource 当我的方法可能不会 必要地 必须进行耗时的操作。它允许我们选择要使用新线程的特定情况。

    使用缓存就是一个很好的例子。你可以有一个 GetResourceAsync 方法,该方法在缓存中查找请求的资源并立即返回(不使用新线程,通过使用 任务完成源 )如果找到了资源。只有在找不到资源的情况下,我们才希望使用一个新线程,并使用 Task.Run() .

    这里可以看到一个代码示例: How to conditionally run a code asynchonously using tasks

        4
  •  36
  •   Sarin    9 年前

    在里面 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");
    
        5
  •  19
  •   superjos    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的这种用法似乎是“不执行代码的Task对象”的另一种情况。

        6
  •  19
  •   Pang Ajmal PraveeN    6 年前

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

        7
  •  5
  •   urig    12 年前

    这是一个真实世界的例子,有一个很好的解释 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();
        });
    }
    
        8
  •  4
  •   nmishr    11 年前

    这可能过于简单化了,但TaskCompletion源允许等待事件。由于只有在事件发生时才会设置tcs.SetResult,因此调用者可以等待任务。

    观看此视频了解更多见解:

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

        9
  •  3
  •   Community Mohan Dere    9 年前

    我使用过的真实世界场景 TaskCompletionSource 是在实现下载队列时。在我的情况下,如果用户启动了100次下载,我不想一次将它们全部关闭,所以我不返回一个策略任务,而是返回一个附加的任务 任务完成源 下载完成后,正在工作的线程将完成任务。

    这里的关键概念是,当客户端要求从任务实际启动时开始启动任务时,我将进行解耦。在这种情况下,因为我不希望客户端必须处理资源管理。

    请注意,只要您使用的是C#编译器(VS 2012+),就可以在.net 4中使用async/await,请参阅 here 了解更多详细信息。

        10
  •  1
  •   Johan Gov    7 年前

    我用过 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.
    }
    
        11
  •  -2
  •   Aloento    4 年前

    Blazor的WebAssemblyHost也使用此功能来阻止.NET虚拟机停止。

    await new TaskCompletionSource().Task;