Search code examples
c#.net-3.5async-awaitinfrastructure

What is the minimum set of types required to compile `async` code?


Out of curiosity, I'm trying to get some simple async/await code to compile under .NET 3.5 Client Profile:

async void AwaitFoo()
{
    await new Foo();
}

class Foo
{
    public IFooAwaiter GetAwaiter() { … }
}

interface IFooAwaiter : System.Runtime.CompilerServices.INotifyCompletion
{
    bool IsCompleted { get; }
    void GetResult();
}

I'm perfectly aware that .NET 3.5 does not support this language feature, as expressed by this compilation error:

Cannot find all types required by the async modifier. Are you targeting the wrong framework version, or missing a reference to an assembly?

I am also aware of the NuGet package Microsoft.Bcl.Async, which does not have support for .NET 3.5.

Question: What is the minimum set of types & type members required for async code to compile? Is this minimal set officially documented; and if so, where? (Note that I'm only interested in successful compilation, not execution.)


What I've got so far:

I've been trying to find this minimum set of types by experiment, which appears to be possible since the compiler reports required, but missing types one by one:

Predefined type System.Runtime.CompilerServices.IAsyncStateMachine is not defined or imported.

Defining the reported type according to MSDN reference pages then leads to the next missing type being reported. I have so far:

  • System.Runtime.CompilerServices.IAsyncStateMachine
  • System.Runtime.CompilerServices.INotifyCompletion (required by the example code above)
  • System.Threading.Tasks.CancellationToken (required by Task)
  • System.Threading.Tasks.TaskCreationOptions (required by Task)
  • System.Threading.Tasks.Task

At this point I stopped, since Task has lots of members, but the compiler does not report exactly which members it requires; it just reports the type as a whole. I might therefore reproduce much more of the type definition than what is actually needed.


Solution

  • I've determined by experiment that the following types are sufficient in order to make the C# 5 compiler process basic async/await code (even when targeting .NET Framework version 2!):

    The most minimal declarations for these that I've found to be acceptable to the C# compiler follow below.

    namespace System.Threading.Tasks
    {
        abstract class Task { }
        abstract class Task<TResult> : Task { }
    }
    
    namespace System.Runtime.CompilerServices
    {
        interface INotifyCompletion { }
        interface ICriticalNotifyCompletion { }
    
        interface IAsyncStateMachine
        {
            void MoveNext();
            void SetStateMachine(IAsyncStateMachine stateMachine);
        }
    
        struct AsyncVoidMethodBuilder
        {
            public static AsyncVoidMethodBuilder Create() { … }
            public void Start<TStateMachine>(ref TStateMachine stateMachine)
                // where TStateMachine : IAsyncStateMachine
                { … }
            public void SetResult() { … }
            public void SetException(Exception exception) { … }
            public void SetStateMachine(IAsyncStateMachine stateMachine) { … }
            public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
                // where TAwaiter : INotifyCompletion
                // where TStateMachine : IAsyncStateMachine
                { … }
            public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
                // where TAwaiter : ICriticalNotifyCompletion
                // where TStateMachine : IAsyncStateMachine
                { … }
        }
    
        struct AsyncTaskMethodBuilder
        {
            public Task Task { get { … } }
            public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
                // where TAwaiter : INotifyCompletion
                // where TStateMachine : IAsyncStateMachine
                { … }
            public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
                // where TAwaiter : ICriticalNotifyCompletion
                // where TStateMachine : IAsyncStateMachine
                { … }
            public static AsyncTaskMethodBuilder Create() { … }
            public void SetException(Exception exception) { … }
            public void SetResult() { … }
            public void SetStateMachine(IAsyncStateMachine stateMachine) { … }
            public void Start<TStateMachine>(ref TStateMachine stateMachine) 
                // where TStateMachine : IAsyncStateMachine
                { … }
        }
    
        struct AsyncTaskMethodBuilder<TResult>
        {
            public static AsyncTaskMethodBuilder<TResult> Create() { … }
            public void Start<TStateMachine>(ref TStateMachine stateMachine) 
                // where TStateMachine : IAsyncStateMachine 
                { … }
            public void SetResult(TResult result) { … }
            public void SetException(Exception exception) { … }
            public void SetStateMachine(IAsyncStateMachine stateMachine) { … }
            public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
                // where TAwaiter : INotifyCompletion
                // where TStateMachine : IAsyncStateMachine 
                { … }
            public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
                // where TAwaiter : ICriticalNotifyCompletion
                // where TStateMachine : IAsyncStateMachine 
                { … }
            public Task<TResult> Task { get { … } }
        }
    }
    

    (I am throwing a NotImplementedException wherever it says { … }.)