How to write an "awaitable" method?
Solution 1:
It's as simple as
Task.Run(() => ExpensiveTask());
To make it an awaitable method:
public Task ExpensiveTaskAsync()
{
return Task.Run(() => ExpensiveTask());
}
The important thing here is to return a task. The method doesn't even have to be marked async. (Just read a little bit further for it to come into the picture)
Now this can be called as
async public void DoStuff()
{
PrepareExpensiveTask();
await ExpensiveTaskAsync();
UseResultsOfExpensiveTask();
}
Note that here the method signature says async
, since the method may return control to the caller until ExpensiveTaskAsync()
returns. Also, expensive in this case means time-consuming, like a web request or similar. To send off heavy computation to another thread, it is usually better to use the "old" approaches, i.e. System.ComponentModel.BackgroundWorker
for GUI applications or System.Threading.Thread
.
Solution 2:
How I would write my own "awaitable" method? Is it as simple as wrapping the code that I want to run asynchronously in a
Task
and returning that?
That is one option, but it's most likely not what you want to do, because it doesn't actually give you many of the advantages of asynchronous code. For more details, see Stephen Toub's Should I expose asynchronous wrappers for synchronous methods?
In general, methods are not awaitable, types are. If you want to be able to write something like await MyMethod()
, then MyMethod()
has to return Task
, Task<T>
or a custom await
able type. Using a custom type is a rare and advanced scenario; using Task
, you have several options:
- Write your method using
async
andawait
. This is useful for composing actions asynchronously, but it can't be used for the inner-mostawait
able calls. - Create the
Task
using one of the methods onTask
, likeTask.Run()
orTask.FromAsync()
. - Use
TaskCompletionSource
. This is the most general approach, it can be used to createawait
able methods from anything that will happen in the future.
Solution 3:
... how I would write my own "awaitable" method.
Returning a Task
is not the only way. You have an option to create a custom awaiter (by implementing GetAwaiter
and INotifyCompletion
), here is a great read: "Await anything". Examples of .NET APIs returning custom awaiters: Task.Yield()
, Dispatcher.InvokeAsync
.
I have some posts with custom awaiters here and here, e.g:
// don't use this in production
public static class SwitchContext
{
public static Awaiter Yield() { return new Awaiter(); }
public struct Awaiter : System.Runtime.CompilerServices.INotifyCompletion
{
public Awaiter GetAwaiter() { return this; }
public bool IsCompleted { get { return false; } }
public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem((state) => ((Action)state)(), continuation);
}
public void GetResult() { }
}
}
// ...
await SwitchContext.Yield();