Search code examples
c#async-awaittask-parallel-librarytpl-dataflowtaskcompletionsource

Wrapping events with TaskCompletionSource vs. BufferBlock<T>


There's a pattern discussed by Lucian here ( Tip 3: Wrap events up in Task-returning APIs and await them ).

I am trying to implement it on a frequently called method that looks something like the contrived code below:

public Task BlackBoxAsync() 
{ 
    var tcs = new TaskCompletionSource<Object>();  // new'ed up every call
    ThreadPool.QueueUserWorkItem(_ => 
    { 
        try 
        { 
            DoSomethingStuff(); 
            tcs.SetResult(null); 
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
    }); 
    return tcs.Task; 
}

I am worried about the performance, when TaskCompletionSource is being new'ed up every call (let's just say I call this method every 100 ms).

I was then thinking of using BufferBlock<T> instead, thinking that it won't be new'ed up every call. So it would look like:

private readonly BufferBlock<object> signalDone; // dummy class-level variable, new'ed up once in CTOR

public Task BlackBoxAsync() 
{ 

    ThreadPool.QueueUserWorkItem(_ => 
    { 
        try 
        { 
            DoSomethingStuff(); 
            signalDone.Post(null);
        } 
        catch(Exception exc) {  } 
    }); 
    return signalDone.ReceiveAsync(); 
}

The calling objects would call it like:

for (var i=0; i<10000; i++) {
 await BlackBoxAsync().ConfigureAwait(false);
}

Does anybody have any thoughts about using BufferBlock<T> instead?


Solution

  • No matter what solution you'll go with, if you want to await a task every time you call this method, creating a new Task is inevitable because tasks aren't reusable. The simplest way to do that is by using TaskCompletionSource.

    So The first option is preferable IMO to using a BufferBlock (which, unsurprisingly, creates a new TaskCompletionSource on ReceiveAsync)


    More to the point, your code seems to simply offload work to the ThreadPool and return a task representing that work. Why aren't you using a simple Task.Run?

    public Task BlackBoxAsync() 
    {
        return Task.Run(() => DoSomethingStuff());
    }