Warm tip: This article is reproduced from stackoverflow.com, please click
.net c# task-parallel-library taskcompletionsource

I was wondering if using TaskCompletitionSource is a bad choice

发布于 2020-03-28 23:15:47

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://docs.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.

Questioner
Dr.Bob
Viewed
75
John Wu 2020-01-31 17:46

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.