75

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. this one, which calls HttpClient.GetStringAsync().

What I'm not so clear on is what goes on in such a method, and 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?

Madis Otenurm
  • 61
  • 1
  • 7
Andrew Stephens
  • 9,413
  • 6
  • 76
  • 152
  • 2
    Or writing an async method that returns a `Task` or `Task`, of course. Async is nicely composable in that way. – Jon Skeet Feb 11 '14 at 11:44
  • 2
    I am just learning C#. I was looking at the same example with GetStringAsync(), and I had the exact same question as the OP, despite being a veteran Java multi-threader. It's a great question and should probably be addressed in the MSDN article which is otherwise very complete. – Pete Sep 03 '15 at 18:50

6 Answers6

96

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.

Janis F
  • 2,637
  • 1
  • 25
  • 36
  • 20
    It is correct that the most natural and usual way to make an awaitable method is to write a method that returns `Task` or `Task<>`. But technically you can also write a method that returns `YourOwnType` provided that `YourOwnType` has a public parameterless non-static instance method called `GetAwaiter()` whose return type is appropriate (find details elsewhere). So `await` is a bit like `foreach`, it works on any type that has a suitable public method. – Jeppe Stig Nielsen Feb 11 '14 at 12:32
  • 1
    That's good to know! Although it probably won't help the readability of your code if you decide to use this approach. – Janis F Feb 11 '14 at 16:45
  • 3
    @JeppeStigNielsen `foreach` requires `IEnumerable`. I'm a little disappointed that `await` uses duck typing when an interface would be more appropriate for the language. The fact that it is 'for compiler use' is a poor excuse. – Gusdor Feb 20 '14 at 14:37
  • 3
    @Gusdor Why, `foreach` does not require any interfaces! Just try these classes: `class ForeachMe { public StrangeType GetEnumerator() { return new StrangeType(); } } class StrangeType { public bool MoveNext() { return true; } public DateTime Current { get { return DateTime.Now; } } }` With them, the code `foreach (var x in new ForeachMe()) { Console.WriteLine(x); }` will work just fine. – Jeppe Stig Nielsen Feb 20 '14 at 15:44
  • @JeppeStigNielsen I wouldn't bet on this being immune to breaking changes. Is that in the language spec? See here http://msdn.microsoft.com/en-us/library/ttw7t8t6.aspx – Gusdor Feb 20 '14 at 16:32
  • 3
    @Gusdor The definitive source is the section [The foreach statement (old version)](http://msdn.microsoft.com/en-us/library/aa664754.aspx) in the official C# Language Specification. Find the same section in the [newest version](http://msdn.microsoft.com/en-us/library/ms228593.aspx). This is well specified; the type need not implement `IEnumerable` or `IEnumerable<>`. – Jeppe Stig Nielsen Feb 20 '14 at 18:51
  • In general, a great answer - but you should not use BackgroundWorker in new code anymore. Use Task.Run() instead - even for heavy computation. More at: http://blog.stephencleary.com/2013/09/taskrun-vs-backgroundworker-conclusion.html – Bernhard Koenig Sep 10 '16 at 19:31
  • I know it is an old post. But I have a question: If async and await does NOT create any additional thread, where are the long running processes run then? I mean there gotta be someone doing the work somewhere. So who runs them? My current explanation is they are being run in a complete different PROCESS by the OS. But I know it is not that correct. Otherwise I would've found same reference in MSDN.Can anyone help clarify this? –  Apr 11 '17 at 13:24
  • `Task.Async()` to my knowlegde actually sends the task to a threadpool. Most OS functions which are marked `async` are I/O-Operations, so they do not need a new thread to run on- they just need to wait for the OS's interrupt. – Janis F Apr 11 '17 at 15:27
18

... 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();
Clonkex
  • 3,373
  • 7
  • 38
  • 55
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 2
    `// don't use this in production` — why exactly? – hypersw Nov 05 '16 at 23:57
  • @hypersw, [here is why](http://stackoverflow.com/questions/15363413/why-was-switchto-removed-from-async-ctp-release). – noseratio Nov 06 '16 at 00:28
  • can't take that for a good “why” but for one specific detail which I've traced down the links, about `await`s not available in `finally`s and the consequences, which anyway does not hold any more. The rest is all very speculative, like bad-looking code (`Task::StartNew` each section rather than `await YieldTo`? only if you haven't tried for yourself), or being semantically unclear (as opposed to `ConfigureAwait(false)`, I assume?). – hypersw Dec 23 '16 at 02:31
  • I guess it all comes down to scenarios. If you got a dozen context switches in one function, it helps. If you only start big background tasks, you'd be safer without. – hypersw Dec 23 '16 at 02:34
  • 1
    The "Await anything" link is dead. I assume this is the new link: https://devblogs.microsoft.com/pfxteam/await-anything/ – reduckted Feb 12 '21 at 05:25
17

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 awaitable type. Using a custom type is a rare and advanced scenario; using Task, you have several options:

  • Write your method using async and await. This is useful for composing actions asynchronously, but it can't be used for the inner-most awaitable calls.
  • Create the Task using one of the methods on Task, like Task.Run() or Task.FromAsync().
  • Use TaskCompletionSource. This is the most general approach, it can be used to create awaitable methods from anything that will happen in the future.
3dGrabber
  • 4,710
  • 1
  • 34
  • 42
svick
  • 236,525
  • 50
  • 385
  • 514
5

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.)

svick
  • 236,525
  • 50
  • 385
  • 514
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
5

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;
}
null
  • 757
  • 1
  • 8
  • 23
Waleed A.K.
  • 1,596
  • 13
  • 13
2

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 state
  • GetResult () to get the result
  • OnCompleted (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 ();
    }
}
mins
  • 6,478
  • 12
  • 56
  • 75