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

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

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

如果我需要一种方法来异步执行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 - 但是我有task。factory。startnew。

问题:

有人能举例说明一个与TaskCompletionSource直接相关的场景吗 而不是假设没有task。factory。startnew ?


当前回答

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

我能想到的最有用的例子是使用Socket。它具有旧的APM和EAP模式,但没有TcpListener和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;
    }

我将套接字传递给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();

其他回答

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

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

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

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

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

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

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

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

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

我能想到的最有用的例子是使用Socket。它具有旧的APM和EAP模式,但没有TcpListener和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;
    }

我将套接字传递给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();