Actual Answer
You do that using TaskCompletionSource
, which has a Promise Task that doesn’t execute any code and only:
“Represents the producer side of a Task unbound to a delegate, providing access to the consumer side through the Task property.”
You return that task to the caller when you start the asynchronous operation and you set the result (or exception/cancellation) when you end it. Making sure the operation is really asynchronous is on you.
Here is a good example of this kind of root of all async method in Stephen Toub’s AsyncManualResetEvent
implementation:
class AsyncManualResetEvent
{
private volatile TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>();
public Task WaitAsync() { return _tcs.Task; }
public void Set() { _tcs.TrySetResult(true); }
public void Reset()
{
while (true)
{
var tcs = _tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref _tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
return;
}
}
}
Background
There are basically two reasons to use async-await
:
- Improved scalability: When you have
I/O
intensive work (or other inherently asynchronous operations), you can call it asynchronously and so you release the calling thread and it’s capable of doing other work in the mean time. - Offloading: When you have
CPU
intensive work, you can call it asynchronously, which moves the work off of one thread to another (mostly used forGUI
threads).
So most of the .Net
framework’s asynchronous calls support async
out of the box and for offloading you use Task.Run
(as in your example). The only case where you actually need to implement async
yourself is when you create a new asynchronous call (I/O
or async synchronization constructs for example).
These cases are extremely rare, which is why you mostly find answers that
“Only explains it with already existing methods and just using
async/await
pattern”
You can go deeper in The Nature of TaskCompletionSource