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

2

8

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.

Allopatric answered 31/7, 2013 at 11:51 Comment(0)
A
3

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

Allopatric answered 2/8, 2013 at 20:47 Comment(0)
E
8

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

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.

Ensemble answered 31/7, 2013 at 11:57 Comment(2)
Thanks. Did you find out about the types in your list yourself, or did you take the list from official documentation? -- Thanks also for the Eduasync link, I've already started reading these blog articles.Allopatric
@stakx: Well I initially built up the list as I went along and implemented bits of Eduasync - but now I'm looking back on the types that I know the compiler uses in the generated code.Ensemble
A
3

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

Allopatric answered 2/8, 2013 at 20:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.