またはから継承することにした場合、タスクの実際の作業を提供するまたはデリゲートは、Task 派生オブジェクトの構築時に指定する必要があり、後で変更できないというフラストレーションに遭遇する可能性があります。これは、基本クラスのコンストラクターが新しく作成されたタスクを実行しない場合でも当てはまります。実際、タスクが開始されたとしても、かなり後で開始される可能性があります。Task
Task<TResult>
Action<Object>
Func<Object,TResult>
Start()
Task
これにより、最終的な作業の完全な詳細が利用可能になる前にインスタンスを作成する必要がある状況で、派生クラスを使用することが難しくなります。
例として、アドホックなTask<TResult>
方法で互いのResult
プロパティにアクセスできるように、共通の目標に取り組んでいるよく知られたノードの非定型ネットワークが考えられます。ネットワーク内の任意のノードでできることを保証する最も簡単な方法は、それらのいずれかを開始する前に、それらすべてを事前に構築することです。これにより、作業グラフの依存関係を分析しようとする問題がうまく回避され、実行時の要因によって、いつ、どのような順序で値が要求されるかを判断できるようになります。Wait()
Result
ここでの問題は、ノードによっては、構築時に作業を定義する機能を提供できない場合があることです。必要なラムダ関数を作成するためResult
に、ネットワーク内の他のタスクからの値を閉じる必要がある場合、必要なTask<TResult>
を提供する はResult
まだ構築されていない可能性があります。また、構築前の段階で以前に構築されていたとしても、まだ構築Start()
されていない他のノードへの依存関係が組み込まれている可能性があるため、まだ呼び出すことはできません。ネットワークを事前に構築することの要点は、このような複雑さを回避することだったことを思い出してください。
これだけでは不十分であるかのように、目的の関数を提供するためにラムダ関数を使用する必要があるのが不便な理由は他にもあります。引数としてコンストラクターに渡されるため、関数はthis
最終的なタスク インスタンスのポインターにアクセスできません。これは、特にラムダが必然的にスコープの下で定義されていることを考えると、醜いコードになります。無関係なthis
ポインター。
先に進むこともできますが、結論としては、派生クラスで拡張機能を定義するときに、ランタイム クロージャの肥大化やその他の煩わしさに耐える必要はありません。それはポリモーフィズムの全体的なポイントを見逃していませんか? 派生クラスの作業デリゲートを通常の方法で定義する方がエレガントTask
です。つまり、基本クラスの抽象関数です。
方法は次のとおりです。秘訣は、独自の引数の 1 つを閉じるプライベート コンストラクターを定義することです。(チェーンされた) 呼び出し先によって渡される引数はnull
、プレースホルダー変数として機能します。これを閉じて、Task
基本クラスに必要なデリゲートを作成できます。コンストラクター本体に入ると、「this」ポインターが使用可能になるため、実際の関数ポインターを閉じた引数に置き換えることができますnull
。外部デリゲートがまだ呼び出されていないため、これを行うのに「遅すぎる」ことはないことに注意してください。
「タスク」から派生する場合:
public abstract class DeferredActionTask : Task
{
private DeferredActionTask(Action _a, CancellationToken ct, TaskCreationOptions opts)
: base(_ => _a(), null, ct, opts)
{
_a = this.action;
}
protected DeferredActionTask(
CancellationToken ct = default(CancellationToken),
TaskCreationOptions opts = TaskCreationOptions.None)
: this(default(Action), ct, opts)
{
}
protected abstract void action();
};
「Task<TResult>」から派生する場合:
public abstract class DeferredFunctionTask<TResult> : Task<TResult>
{
private DeferredFunctionTask(Func<TResult> _f, CancellationToken ct, TaskCreationOptions opts)
: base(_ => _f(), null, ct, opts)
{
_f = this.function;
}
protected DeferredFunctionTask(
CancellationToken ct = default(CancellationToken),
TaskCreationOptions opts = TaskCreationOptions.None)
: this(default(Func<TResult>), ct, opts)
{
}
protected abstract TResult function();
};
構築されたTask
インスタンスの他の使用法と同様に、 は構築時に自動的に開始されないことに注意してください。そのため、この手法では、後で明示的に呼び出す必要がTask
ありますStart()
。もちろん、上で説明したように、ここが要点です。
最後に、プライベート コンストラクターが常にベース コンストラクターnull
のstate
引数を渡すようにしたこと、およびこれにより、読み取り専用プロパティが有用な値にTask
設定されることが実質的に防止されることに注意してください。AsyncState
必要に応じて、このような値のパススルーを含めるようにこれを変更できますが、ここでも重要な点は、起動データが事前に決定されているという要件を排除することです。を呼び出す前の任意の時点で、関連するインスタンス データを設定する独自の派生クラス全体がある場合、論理的に無関係な時点で、おそらく非常に事前に、1 つの「特別な" データ パラメーターを使用して、タスクの最終的な有用な作業の詳細を表します。Start