Search code examples
c#.nettask-parallel-librarytaskcompletionsource

I was wondering if using TaskCompletitionSource is a bad choice


I have to add here I am not a practiced questioner on Stackoverflow so I am glad for feedback concerning why my question might not fit here.

Is awaiting a TaskCompletitionSource a bad thing when wrapping a not async call?

Here is my use case:

I have a handler class which calls a function Func<T, Task> callback when an event occurs. The handler gets called from outside my Application and notifies my UI. There are two methods A and B which get used as callback. A where an async HTTP Client gets called and B where I do computation. In both cases the await call will unfreeze the UI and then properties get updated.

A:

public async Task A(){
 result = await CallHttpClient(...) // unfreeze UI
 // ... copy image bytes and update UI (long running not async)
 // release bytes in calling method
}

B:

public async Task B(){
 var  tcs = new TaskCompletionSource<bool>();
 await tcs.Task; // unfreeze UI
 // ... copy image bytes and update UI (long running not async)
 tcs.SetResult(true); // release bytes in calling method
}

My question here, is it a bad practice to use TaskCompletionSource to wrap a not async call?

The Documentation states the following.

If you want to create a task wrapper for an existing asynchronous operation or event, use TaskCompletionSource. https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming

Another possibility is to call Task.Run(), but it feels even worse to me. Not using Async would result in a freezing UI which is not really what I want although it might be the cleanest solution.

-------> Update

As state by others the Task.Run() is perfectly fine here.

I should note that my B: look different B:

public async Task B(...){
 var  tcs = new TaskCompletionSource<bool>();
 // ... duplicate bytes
 tcs.SetResult(true); // release bytes in calling method
 await tcs.Task; // unfreeze UI
 // ... copy image bytes and update UI (long running not async)
}

Find better option with Task.Run() below.

I should also note that when the method leaves the bytes (not shown in the example) are released.


Solution

  • There is no way to do a CPU-bound task in the background without some sort of multithreading.

    This code...

    var  tcs = new TaskCompletionSource<bool>();
    await tcs.Task; // unfreeze UI
    // ... copy image bytes and update UI (long running not async)
    tcs.SetResult(true); // release bytes in calling method
    

    ...will block on the await because SetResult is not called until after, resulting in a sort of deadlock.

    I suppose you could do something nutty like this

    var  tcs = new TaskCompletionSource<bool>();
    Parallel.Invoke
    (
        () => await tcs.Task,
        () => {
             // ... copy image bytes and update UI (long running not async)
            tcs.SetResult(true); // release bytes in calling method
        }
    );
    

    But I'm not sure that would work either. The standard way to do this would be

    await Task.Run( () => {
        // ... copy image bytes and update UI (long running not async)
    });
    

    ...which is certainly easier to follow, and is what Task.Run() is was meant for.