Do Tasks generated by TaskCompletionSource need to be Dispose()d?
Asked Answered
M

1

6

I'm using TaskCompletionSource in my software to distribute network packets to async/await methods. So in my code, there are various points where the software needs to wait for a network packet getting demultiplexed from the receive handler and handed over to a method async Wait(). There can be many, many packets per second and I'm deciding if I want to push the packet to TaskCompletionSource or put it into a Queue. So, whenever theres no TaskCompletionSource I will create a new one, which leads to a new Task object.

According to this question Do I need to dispose of a Task? and according to this blog Parallel Programming with .NET Tasks don't need be Disposed. However, I'm sometimes instantiating many thousand TaskCompletionSource per second. The detailed answer in the linked blog also says, that Task may use internally WaitHandle. Now I have the strong feeling that my case is the exact case, where I should use Dispose on the tasks from the TaskCompletionSource.

This is how I wait for a new packet. This method will be calles with await and can also be called heavily in parallel:

public async Task<Packet> Wait()
{
    Packet packet;

    lock (sync)
        if (packets.TryDequeue(out packet))
            return packet;
        else
            waiter = new TaskCompletionSource<Packet>();

    return await waiter.Task;
}

My method of pushing packets from the network handler looks like this:

public void Poke(Packet packet)
{
    lock (sync)
        if (waiter == null)
            packets.Enqueue(packet);
        else
            waiter.SetResult(packet);
}

I'm on .NET Core >= 2.2. The blog entry states that the behavior of WaitHandle also changed in .NET 4.5.

Question: Do I need to Dispose Tasks in this specific scenario? Will I create many Handles, if I don't Dispose the Tasks created by TaskCompletionSource when receiving many packets coming along this code path? Is this the scenario the blog entry warned me about?

Please refrain from telling me, that this method is a bad choice as long as you can't tell me a better method being very compatible to async/await pattern and also being able to distribute those packets to various selected listeners. Please also don't tell me, that creating many objects because of many network packets is generally a bad idea.

Mika answered 18/2, 2019 at 21:49 Comment(0)
S
7

Is this the scenario the blog entry warned me about?

The question of "do I need to dispose this Task?" can only be answered by how the task is consumed. In particular, consider this quote from the blog post:

the only way the WaitHandle will be allocated is if you explicitly ask for the Task’s IAsyncResult.AsyncWaitHandle, and that should be quite rare.

Reed's answer was true at the time, but it's no longer the case that continuations use AsyncWaitHandle. These days, nothing will use AsyncWaitHandle implicitly, so you should only consider Disposeing Tasks if your consuming code is treating the task as an IAsyncResult and accessing the AsyncWaitHandle property. And even then, you should only consider disposing them; it's not strictly necessary.

Please refrain from telling me, that this method is a bad choice as long as you can't tell me a better method being very compatible to async/await pattern and also being able to distribute those packets to various selected listeners.

I would recommend building an async-compatible producer/consumer queue as a separate type; I think this would help your code be clearer. You could use BufferBlock<T>, an async queue type from my AsyncEx library, or build your own using an async-compatible monitor.

Also, if you regularly expect your Wait() method to already have a packet available, then consider using ValueTask<T>.

Sorcerer answered 19/2, 2019 at 14:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.