web-dev-qa-db-ja.com

C#7では、非同期で使用するために、どのように「自分のロール」のタスクのようなタイプを実行できますか?

C#7のあまり話題にならない機能の1つは、「一般化された非同期の戻り値の型」で、Microsoftは次のように説明しています。

非同期メソッドからTaskオブジェクトを返すと、特定のパスでパフォーマンスのボトルネックが発生する可能性があります。タスクは参照型なので、それを使用することはオブジェクトを割り当てることを意味します。 async修飾子を使用して宣言されたメソッドがキャッシュされた結果を返すか、同期的に完了する場合、余分な割り当ては、コードのパフォーマンスクリティカルセクションでかなりの時間コストになる可能性があります。これらの割り当てがタイトなループで発生すると、非常にコストがかかる可能性があります。

新しい言語機能は、非同期メソッドがTaskTask<T>およびvoidに加えて他のタイプを返す可能性があることを意味します。返されるタイプは、非同期パターンを満たしている必要があります。つまり、GetAwaiterメソッドにアクセスできる必要があります。具体的な例の1つとして、この新しい言語機能を利用するために、.NETフレームワークにValueTaskタイプが追加されています。

それは素晴らしいように聞こえますが、ストックValueTask<T>タイプを使用しないだけの例を見つけることはできません。私は自分のタスクのようなタイプを作りたいです。具体的には、Task<T>のように動作するが、より機能的なスタイルのエラー処理を備えたタイプが必要です。

これが私のプロジェクトで機能エラー処理に使用しているタイプです:

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;
    }
}

これが私のカスタムのawaitableタイプが次のようになるはずだと思うものです:

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
}

これは、非同期の戻り値の型として使用するまで、すべてコンパイルされます。

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

このメソッドはコンパイラエラーを発生します非同期メソッドの戻り値の型はvoid、Task、またはTaskでなければなりません

これをコンパイルするにはどうすればよいですか?


更新:

確認のため、私はVS 2017リリースを3/7から使用しており、ローカル関数など、プロジェクトの他のC#7機能を使用できます。

私もValueTaskを使用してみましたが、同じコンパイラエラーが発生します。

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

何が起こっているかについていくつかの光を当てる別の投稿があります。
VS2017 RCで新しい非同期セマンティクスを機能させるにはどうすればよいですか?

明らかに、別の「メソッドビルダー」タイプを定義する必要があり、特別な属性を待機可能なタイプに適用する必要があります。私が本当にこれを掘り下げる時間があるかどうかわかりません。 「言語機能」というよりは、メタプログラミングハッカーに似ています。

31
JamesFaix

良いチュートリアルはまだ見つかりませんでした。しかし、そのようなタスクのようなタイプを作成する compiler unittests を見ることができます( "[AsyncMethodBuilder"を探します)。

開始点は、タイプを作成し、[AsyncMethodBuilder(typeof(MyTaskBuilder))]のような属性でタスクのようにマークすることです。次に、独自のMyTaskBuilderタイプを定義する必要があります。特定のパターンを実装する必要があります(以下を参照)。これは、通常のAsyncMethodBuilderをサポートする通常の 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 { }
}

更新: タスクのようなタイプの小さな仕様 がコンパイラーのドキュメントに追加されました。

16
Julien Couvreur