Chris Chris - 14 days ago 7
C# Question

Using TaskCompletionSource with a queue and background thread

In my ASP.NET API, I'm receiving a series of messages to be persisted. In this particular case, the responsiveness of the API supersedes the importance of any individual message, so instead of writing directly to my data store, I'm pushing the incoming messages to a Queue and processing them ASAP on a background thread.

In some cases, I may have a need to confirm the success or failure of the write operation, so I wrap my message in a custom MessageContext object that uses the following code:

public class WriteMessageContext
{
public Message Message { get; private set; }
TaskCompletionSource<bool> complete = new TaskCompletionSource<bool>();

public WriteMessageContext(Message message)
{
Message = message;
}

public Task<bool> WaitForInsert()
{
return complete.Task;
}

public void Success()
{
complete.SetResult(true);
}

public void Error()
{
complete.SetResult(false);
}
}


The Success and Error methods are intended to be called by the worker thread that processes each queued message in the background. Then, in my controller, I can do:

var ctx = new WriteMessageContext(msg);
queue.Enqueue(ctx);

// optionally...
if (await ctx.WaitForIt()) {
// successful
} else {
// or not
}


Is this an appropriate use of TaskCompletionSource + async/await, or am I bastardizing it with wreckless abandon? Are there any potential issues I should be concerned with doing it this way?

Answer

For me, it's a valid usage of TaskCompletionSource<T>.

BTW, maybe you get surprised because there's some hidden feature/gem present in Task Parallel Library (TPL) called data-flow blocks.

Actually, there's one that can fit your requirements: BufferBlock<T>:

The BufferBlock<T> class represents a general-purpose asynchronous messaging structure. This class stores a first in, first out (FIFO) queue of messages that can be written to by multiple sources or read from by multiple targets. When a target receives a message from a BufferBlock<T> object, that message is removed from the message queue. Therefore, although a BufferBlock<T> object can have multiple targets, only one target will receive each message. The BufferBlock<T> class is useful when you want to pass multiple messages to another component, and that component must receive each message.