I\'m finally looking into the async & await keywords, which I kind of "get", but all the examples I\'ve seen call async methods in the .Net framework, e.g. thi
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:
async
and await
. This is useful for composing actions asynchronously, but it can't be used for the inner-most await
able calls.Task
using one of the methods on Task
, like Task.Run()
or Task.FromAsync()
.await
able methods from anything that will happen in the future.... 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();
Yes, technically you only need to return a Task
or Task<Result>
from an async
method to implement an awaitable method.
This supports the Task-Based Asynchronous Pattern.
There are several ways of implementing the TAP, however. See Implementing the Task-based Asynchronous Pattern for details.
(But all these implementations still return Task
or Task<Result>
, of course.)
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
.
If you don't want to use a Task
, you may write a completely customized awaitable object. Such object is one implementing a method GetAwaiter ()
returning an object implementing INotifyCompletion
, which can be the object itself.
More: INotifyCompletion
The awaiter implements:
IsCompleted
is get the stateGetResult ()
to get the resultOnCompleted (Action continuation)
to set the continuation delegate.The awaitable object contains some method for actual payload (e.g. below, the method is Run
).
class Program {
// Need to change the declaration of Main() in order to use 'await'
static async Task Main () {
// Create a custom awaitable object
MyAwaitable awaitable = new MyAwaitable ();
// Run awaitable payload, ignore returned Task
_ = awaitable.Run ();
// Do some other tasks while awaitable is running
Console.WriteLine ("Waiting for completion...");
// Wait for completion
await awaitable;
Console.WriteLine ("The long operation is now complete. " + awaitable.GetResult());
}
}
public class MyAwaitable : INotifyCompletion {
// Fields
private Action continuation = null;
private string result = string.Empty;
// Make this class awaitable
public MyAwaitable GetAwaiter () { return this; }
// Implementation of INotifyCompletion for the self-awaiter
public bool IsCompleted { get; set; }
public string GetResult () { return result; }
public void OnCompleted (Action continuation) {
// Store continuation delegate
this.continuation = continuation;
Console.WriteLine ("Continuation set");
}
// Payload to run
public async Task Run () {
Console.WriteLine ("Computing result...");
// Wait 2 seconds
await Task.Delay (2000);
result = "The result is 10";
// Set completed
IsCompleted = true;
Console.WriteLine ("Result available");
// Continue with the continuation provided
continuation?.Invoke ();
}
}
Just convert your method to Task. Like @Romiox I usually use this extention:
public static partial class Ext
{
#region Public Methods
public static Task ToTask(Action action)
{
return Task.Run(action);
}
public static Task<T> ToTask<T>(Func<T> function)
{
return Task.Run(function);
}
public static async Task ToTaskAsync(Action action)
{
return await Task.Run(action);
}
public static async Task<T> ToTaskAsync<T>(Func<T> function)
{
return await Task.Run(function);
}
#endregion Public Methods
}
Now let we say you have
void foo1()...
void foo2(int i1)...
int foo3()...
int foo4(int i1)...
...
Then you can declare your[async Method] like @Romiox
async Task foo1Async()
{
return await Ext.ToTask(() => foo1());
}
async Task foo2Async(int i1)
{
return await Ext.ToTask(() => foo2(i1));
}
async Task<int> foo3Async()
{
return await Ext.ToTask(() => foo3());
}
async Task<int> foo4Async(int i1)
{
return await Ext.ToTask(() => foo4(i1));
}
OR
async Task foo1Async()
{
return await Ext.ToTaskAsync(() => foo1());
}
async Task foo2Async(int i1)
{
return await Ext.ToTaskAsync(() => foo2(i1));
}
async Task<int> foo3Async()
{
return await Ext.ToTaskAsync(() => foo3());
}
async Task<int> foo4Async(int i1)
{
return await Ext.ToTaskAsync(() => foo4(i1));
}
...
Now you can use async and await for any of the fooAsync methods e.g. foo4Async
async Task<int> TestAsync () {
///Initial Code
int m = 3;
///Call the task
var X = foo4Async(m);
///Between
///Do something while waiting comes here
///..
var Result = await X;
///Final
///Some Code here
return Result;
}