Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
133 views
in Technique[技术] by (71.8m points)

c# - In C#7, how can I "roll my own" Task-like type to use with async?

One of the less-talked-about features of C#7 is "generalized async return types", which is described by Microsoft as:

Returning a Task object from async methods can introduce performance bottlenecks in certain paths. Task is a reference type, so using it means allocating an object. In cases where a method declared with the async modifier returns a cached result, or completes synchronously, the extra allocations can become a significant time cost in performance critical sections of code. It can become very costly if those allocations occur in tight loops.

The new language feature means that async methods may return other types in addition to Task, Task<T> and void. The returned type must still satisfy the async pattern, meaning a GetAwaiter method must be accessible. As one concrete example, the ValueTask type has been added to the .NET framework to make use of this new language feature:

That sounds great, but I cannot for the life of my find any example that doesn't just use the stock ValueTask<T> type. I want to make my own Task-like type. Specifically I want a type that behaves like a Task<T>, but with a more functional style of error handling.

Here is the type I am using for functional error handling in my project:

public class Try<T> {
    public T Data { get; }
    public Exception Error { get; }

    public bool HasData => Error == null;
    public bool HasError => Error != null;

    public Try(T data) {
        Data = data;
    }

    public Try(Exception error) {
        Error = error;
    }
}

Here is what I think my custom awaitable type should look like:

public class TryTask<T> : Task<Try<T>> {

    public TryTask(Func<Try<T>> func)
        : base(func) { }

    //GetAwaiter is defined on base type, so we should be okay there
}

This all compiles, until I try to use it as an async return type:

async TryTask<int> DoWhatever() {
    return await new TryTask<int>(() => new Try<int>(1));
}

This method will give the compiler error The return type of an async method must be void, Task, or Task.

How do I make this or something like it compile?


Update:

To confirm, I am using the VS 2017 release from 3/7, and I am able to use other C#7 features in my project, such as local functions.

I have also tried using ValueTask and am getting the same compiler error.

static async ValueTask<int> DoWhatever() {
    return await new ValueTask<int>(1);          
}

Here's another post that sheds some light on whats going on.
How do I get the new async semantics working in VS2017 RC?

Apparently a separate "method builder" type needs to be defined and special attributes need to be applied to the awaitable type. I don't know if I really have time to dig into this. It seems more like metaprogramming hackery than a "language feature".

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I couldn't find any good tutorial yet. But you can look at the compiler unittests which create such task-like types (look for "[AsyncMethodBuilder").

The starting point is to create a type and mark it as task-like with an attribute like [AsyncMethodBuilder(typeof(MyTaskBuilder))]. Then you need to define your own MyTaskBuilder type. It must implement a certain pattern (see below). That is the same pattern implemented by the regular AsyncMethodBuilder type which supports regular Task.

class MyTaskBuilder
{
    public static MyTaskBuilder Create() => null;
    public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { }
    public void SetStateMachine(IAsyncStateMachine stateMachine) { }
    public void SetResult() { }
    public void SetException(Exception exception) { }
    public MyTask Task => default(MyTask);
    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 { }
}

Update: a small spec for task-like types was added to the compiler documents.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...