简体   繁体   English

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

[英]When should TaskCompletionSource<T> be used?

AFAIK, all it knows is that at some point, its SetResult or SetException method is being called to complete the Task<T> exposed through its Task property. AFAIK,它所知道的是,在某个时候,它的SetResultSetException方法被调用以完成通过其Task属性公开的Task<T>

In other words, it acts as the producer for a Task<TResult> and its completion.换句话说,它充当Task<TResult>及其完成的生产者。

I saw here the example:我在这里看到了这个例子:

If I need a way to execute a Func<T> asynchronously and have a Task<T> to represent that operation.如果我需要一种方法来异步执行Func<T>并有一个Task<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; 
}

Which could be used if I didn't have Task.Factory.StartNew - But I do have Task.Factory.StartNew .如果我没有Task.Factory.StartNew可以使用它 - 但我确实Task.Factory.StartNew

Question:问题:

Can someone please explain by example a scenario related directly to TaskCompletionSource and not to a hypothetical situation in which I don't have Task.Factory.StartNew ?有人可以举例说明一个与TaskCompletionSource直接相关的场景,而不是我没有Task.Factory.StartNew假设情况吗?

I mostly use it when only an event based API is available ( for example Windows Phone 8 sockets ):我主要在只有基于事件的 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;
}

So it's especially useful when used together with the C#5 async keyword.因此,它与 C#5 的async关键字一起使用时特别有用。

In my experiences, TaskCompletionSource is great for wrapping old asynchronous patterns to the modern async/await pattern.根据我的经验, TaskCompletionSource非常适合将旧的异步模式包装到现代的async/await模式中。

The most beneficial example I can think of is when working with Socket .我能想到的最有益的例子是使用Socket时。 It has the old APM and EAP patterns, but not the awaitable Task methods that TcpListener and TcpClient have.它具有旧的 APM 和 EAP 模式,但没有TcpListenerTcpClient具有的可awaitable Task方法。

I personally have several issues with the NetworkStream class and prefer the raw Socket .我个人对NetworkStream类有几个问题,并且更喜欢原始Socket Being that I also love the async/await pattern, I made an extension class SocketExtender which creates several extension methods for Socket .因为我也喜欢async/await模式,所以我创建了一个扩展类SocketExtender ,它为Socket创建了几个扩展方法。

All of these methods make use of TaskCompletionSource<T> to wrap the asynchronous calls like so:所有这些方法都使用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;
    }

I pass the socket into the BeginAccept methods so that I get a slight performance boost out of the compiler not having to hoist the local parameter.我将socket传递给BeginAccept方法,这样我就可以从编译器中获得轻微的性能提升,而不必提升本地参数。

Then the beauty of it all:那么这一切的美丽:

 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();

To me, a classic scenario for using TaskCompletionSource is when it's possible that my method won't necessarily have to do a time consuming operation.对我来说,使用TaskCompletionSource的一个经典场景是我的方法可能不一定需要执行耗时的操作。 What it allows us to do is to choose the specific cases where we'd like to use a new thread.它允许我们做的是选择我们想要使用新线程的特定情况。

A good example for this is when you use a cache.一个很好的例子是当你使用缓存时。 You can have a GetResourceAsync method, which looks in the cache for the requested resource and returns at once (without using a new thread, by using TaskCompletionSource ) if the resource was found.您可以使用GetResourceAsync方法,该方法在缓存中查找请求的资源,如果找到资源,则立即返回(不使用新线程,使用TaskCompletionSource )。 Only if the resource wasn't found, we'd like to use a new thread and retrieve it using Task.Run() .只有在找不到资源的情况下,我们才会使用新线程并使用Task.Run()检索它。

A code example can be seen here: How to conditionally run a code asynchonously using tasks可以在此处查看代码示例:如何使用任务有条件地异步运行代码

In this blog post , Levi Botelho describes how to use the TaskCompletionSource to write an asynchronous wrapper for a Process such that you can launch it and await its termination.这篇博文中,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;
}

and its usage及其用法

await RunProcessAsync("myexecutable.exe");

TaskCompletionSource is used to create Task objects that don't execute code. TaskCompletionSource用于创建不执行代码的Task对象。 In real world scenarios, TaskCompletionSource is ideal for I/O bound operations.在现实世界的场景中, TaskCompletionSource非常适合 I/O 绑定操作。 This way, you get all the benefits of tasks (eg return values, continuations, etc) without blocking a thread for the duration of the operation.这样,您可以获得任务的所有好处(例如返回值、延续等),而不会在操作期间阻塞线程。 If your "function" is an I/O bound operation, it isn't recommended to block a thread using a new Task .如果您的“函数”是 I/O 绑定操作,则不建议使用新任务阻塞线程。 Instead, using TaskCompletionSource , you can create a slave task to just indicate when your I/O bound operation finishes or faults.相反,使用TaskCompletionSource ,您可以创建一个从属任务来指示您的 I/O 绑定操作何时完成或出现故障。

It looks like no one mentioned, but I guess unit tests too can be considered real life enough.看起来没人提到,但我想单元测试也可以被认为是现实生活中的足够多的东西。

I find TaskCompletionSource to be useful when mocking a dependency with an async method.我发现TaskCompletionSource在使用异步方法模拟依赖项时很有用。

In actual program under test:在实际测试程序中:

public interface IEntityFacade
{
  Task<Entity> GetByIdAsync(string id);
}

In unit tests:在单元测试中:

// 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);
  // ...
}

After all, this usage of TaskCompletionSource seems another case of "a Task object that does not execute code".毕竟,TaskCompletionSource 的这种用法似乎是“不执行代码的任务对象”的另一种情况。

There's a real world example with a decent explanation in thispost from the "Parallel Programming with .NET" blog . 在“Parallel Programming with .NET”博客的这篇文章中有一个真实世界的例子,其中有一个不错的解释。 You really should read it, but here's a summary anyway.您真的应该阅读它,但无论如何这里是一个摘要。

The blog post shows two implementations for:博客文章显示了以下两种实现:

"a factory method for creating “delayed” tasks, ones that won't actually be scheduled until some user-supplied timeout has occurred." “一种用于创建“延迟”任务的工厂方法,这些任务在发生某些用户提供的超时之前实际上不会被安排。”

The first implementation shown is based on Task<> and has two major flaws.所示的第一个实现基于Task<>并且有两个主要缺陷。 The second implementation post goes on to mitigate these by using TaskCompletionSource<> .第二个实现帖子继续通过使用TaskCompletionSource<>来缓解这些问题。

Here's that second implementation:这是第二个实现:

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();
    });
}

This may be oversimplifying things but the TaskCompletion source allows one to await on an event.这可能过于简单化了,但 TaskCompletion 源允许人们等待事件。 Since the tcs.SetResult is only set once the event occurs, the caller can await on the task.由于 tcs.SetResult 仅在事件发生时设置,调用者可以等待任务。

Watch this video for more insights:观看此视频以获得更多见解:

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

I real world scenario where I have used TaskCompletionSource is when implementing a download queue.我使用TaskCompletionSource的真实世界场景是在实现下载队列时。 In my case if the user starts 100 downloads I don't want to fire them all off at once and so instead of returning a strated task I return a task attached to TaskCompletionSource .在我的情况下,如果用户开始 100 次下载,我不想一次将它们全部关闭,因此我没有返回一个分层任务,而是返回一个附加到TaskCompletionSource的任务。 Once the download gets completed the thread that is working the queue completes the task.一旦下载完成,队列中的线程就会完成任务。

The key concept here is that I am decoupling when a client asks for a task to be started from when it actually gets started.这里的关键概念是,当客户要求从实际开始时开始执行任务时,我正在解耦。 In this case because I don't want the client to have to deal with resource management.在这种情况下,因为我不希望客户端必须处理资源管理。

note that you can use async/await in .net 4 as long as you are using a C# 5 compiler (VS 2012+) see here for more details.请注意,只要您使用 C# 5 编译器(VS 2012+),您就可以在 .net 4 中使用 async/await,请参阅此处了解更多详细信息。

I've used TaskCompletionSource to run a Task until it is cancelled.我使用TaskCompletionSource来运行一个任务,直到它被取消。 In this case it's a ServiceBus subscriber that I normally want to run for as long as the application runs.在这种情况下,它是一个 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.
}

Blazor 的 WebAssemblyHost 也使用它来防止 .NET VM 停止。

await new TaskCompletionSource().Task;

AFAIK, all it knows is that at some point, its SetResult or SetException method is being called to complete the Task<T> exposed through its Task property. AFAIK,它所知道的只是在某个时候,它的SetResultSetException方法被调用以完成通过其Task属性公开的Task<T>

In other words, it acts as the producer for a Task<TResult> and its completion.换句话说,它充当Task<TResult>及其完成的生产者。

I saw here the example:我在这里看到了示例:

If I need a way to execute a Func asynchronously and have a Task to represent that operation.如果我需要一种异步执行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; 
}

Which could be used *if I didn't have Task.Factory.StartNew - But I do have Task.Factory.StartNew . *如果我没有Task.Factory.StartNew可以使用-但我确实Task.Factory.StartNew

Question:问题:

Can someone please explain by example a scenario related directly to TaskCompletionSource and not to a hypothetical situation in which I don't have Task.Factory.StartNew ?有人可以举例说明与TaskCompletionSource直接相关的场景, TaskCompletionSource与我没有Task.Factory.StartNew假设情况Task.Factory.StartNew吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM