Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 746
stakx - no longer contributing Avatar asked Jul 31 '13 11:07

stakx - no longer contributing


2 Answers

In terms of the C# compiler, you also need:

  • AsyncTaskMethodBuilder
  • AsyncTaskMethodBuilder<TResult>
  • AsyncVoidMethodBuilder
  • AsyncStateMachineAttribute
  • Possibly ICriticalNotifyCompletion although I believe that' just used by the builder.

I wouldn't expect that either TaskCreationOptions or CancellationToken would actually be required - I can't think where they'd be used in the generated code.

Fundamentally though, you really need the whole TPL support for it to work - just having it compile isn't going do it for you. If you're only interested in this for curiosity, that's a different matter. You might be interested in my Eduasync blog series, which was a crude version of getting the CTP release of the compiler to work without the AsyncCtpLibrary.dll assembly (against .NET 4.0) - basically supplying all the relevant types.

The source code won't work against the C# 5 compiler as things changed a bit for the final release, but most of the concepts have stayed the same.

like image 199
Jon Skeet Avatar answered Nov 11 '22 15:11

Jon Skeet


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!):

  • IAsyncStateMachine
  • INotifyCompletion (no member declarations needed)
  • ICriticalNotifyCompletion (no member declarations needed)
  • AsyncVoidMethodBuilder
  • AsyncTaskMethodBuilder
  • AsyncTaskMethodBuilder<TResult>
  • Task (no member declarations needed)
  • Task<TResult> (no member declarations needed)

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 { … }.)

like image 21
stakx - no longer contributing Avatar answered Nov 11 '22 13:11

stakx - no longer contributing