231

AFAIK,它所知道的是,在某个时候,它的SetResultorSetException方法被调用以完成Task<T>通过其Task属性公开的操作。

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

我在这里看到了这个例子:

如果我需要一种方法来Func<T>异步执行 a 并有 aTask<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情况相关的场景吗?

4

10 回答 10

262

我主要在只有基于事件的 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;
}

async因此,它与 C#5关键字一起使用时特别有用。

于 2013-03-09T22:31:23.157 回答
84

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

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

我个人在课堂上有几个问题,NetworkStream并且更喜欢 raw 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();
于 2014-02-01T18:29:00.160 回答
39

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

一个很好的例子是当你使用缓存时。您可以有一个GetResourceAsync方法,该方法在缓存中查找请求的资源,如果找到资源,则立即返回(不使用新线程,使用TaskCompletionSource)。只有在找不到资源的情况下,我们才会使用新线程并使用Task.Run().

可以在此处查看代码示例:如何使用任务有条件地异步运行代码

于 2013-03-10T07:16:19.557 回答
30

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

及其用法

await RunProcessAsync("myexecutable.exe");
于 2014-08-30T12:09:56.827 回答
16

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

于 2014-04-25T09:53:13.357 回答
16

看起来没人提到,但我想单元测试也可以被认为是现实生活中的足够多的东西。

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

于 2015-07-10T11:26:23.920 回答
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();
    });
}
于 2014-07-14T13:17:38.077 回答
4

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

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

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

于 2015-04-02T18:58:18.990 回答
3

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

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

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

于 2014-01-08T19:20:32.093 回答
1

我曾经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.
}
于 2019-01-03T09:28:33.977 回答