Если вы делаете решите унаследовать от Task
или Task<TResult>
, вы можете столкнуться с разочарованием, что делегат Action<Object>
или Func<Object,TResult>
, обеспечивающий фактическую работу для задачи должно быть указано во время создания объекта, производного от Задачи, и не может быть изменено позже. Это верно, даже если конструктор (ы) базового класса не Start()
вновь созданная задача, и на самом деле она может быть запущена намного позже, если вообще вообще не будет.
Это затрудняет использование класса Task
, полученного в ситуациях, когда экземпляры должны быть созданы до получения полной информации о его возможной работе.
Примером может служить аморфная сеть хорошо известных Task<TResult>
узлов, работающих на общую цель, так что они получают доступ к свойствам Result
друг друга ad-hoc способом. Самый простой способ гарантировать, что вы можете Wait()
на любом произвольном узле в сети, - это предварительно создать все из них перед запуском любого из них. Это аккуратно устраняет проблему попыток анализа зависимостей рабочего графа и позволяет факторам времени выполнения определять, когда, если и в каком порядке требуются Result
значения.
Проблема здесь в том, что для некоторых узлов вы не сможете предоставить функцию, которая определяет работу во время сборки. Если создание необходимой лямбда-функции требует закрытия значений Result
из других задач в сети, то Task<TResult>
, обеспечивающий желаемое значение Result
, возможно, еще не был создан. И даже если случится, что он был построен ранее на этапе подготовки, вы еще не можете вызвать Start()
, поскольку он может включать зависимости от других узлов, которые этого не сделали. Помните, что весь смысл предварительного построения сети состоял в том, чтобы избежать сложностей, подобных этим.
Как будто этого было недостаточно, есть и другие причины, по которым неудобно использовать лямбда-функцию для обеспечения желаемой функции. Поскольку он передается в конструктор в качестве аргумента, функция не может получить доступ к указателю this
возможного экземпляра задачи, что приводит к уродливому коду, особенно учитывая, что лямбда-выражение обязательно определяется в области действия - и, возможно, непреднамеренного закрытия над - какой-то не связанный this
указатель.
Я мог бы продолжить, но суть в том, что вам не нужно терпеть раздувание закрытия среды выполнения и другие неприятности при определении расширенной функциональности в производном классе. Разве это не пропускает весь смысл полиморфизма? Было бы более элегантно определить рабочий делегат класса Task
, полученного обычным способом, а именно абстрактной функции в базовом классе.
Вот как это сделать. Хитрость заключается в том, чтобы определить приватный конструктор, который закрывается над одним из своих собственных аргументов. Аргумент, изначально установленный на null
, действует как переменная-заполнитель, которую можно закрыть, чтобы создать делегат, необходимый базовому классу Task
. Как только вы окажетесь в теле конструктора, указатель 'this' станет доступен, так что вы можете установить фактический указатель на функцию.
Для получения из «Задания»:
public abstract class DeferredActionTask : Task
{
private DeferredActionTask(DeferredActionTask _this)
: base(_ => ((Func<DeferredActionTask>)_)().action(),
(Func<DeferredActionTask>)(() => _this))
{
_this = this;
}
protected DeferredActionTask() : this(null) { }
protected abstract void action();
};
Для получения из 'Задания ':
public abstract class DeferredFunctionTask<TResult> : Task<TResult>
{
private DeferredFunctionTask(DeferredFunctionTask<TResult> _this)
: base(_ => ((Func<DeferredFunctionTask<TResult>>)_)().function(),
(Func<DeferredFunctionTask<TResult>>)(() => _this))
{
_this = this;
}
protected DeferredFunctionTask() : this(null) { }
protected abstract TResult function();
};
[Редактировать: упрощенный]
Эти упрощенные версии еще больше уменьшают посторонние замыкания, закрываясь непосредственно по методу action или function производных экземпляров. Это также освобождает AsyncState
в базовом классе на случай, если вы захотите его использовать. Вряд ли это кажется необходимым, поскольку теперь у вас есть весь собственный производный класс; соответственно, AsyncState
не передается в рабочую функцию. Если вам это нужно, вы всегда можете просто взять его из свойства базового класса. Наконец, различные необязательные параметры теперь могут быть переданы в базовый класс Task
.
Для получения из «Задачи»:
public abstract class DeferredActionTask : Task
{
private DeferredActionTask(Action _a, Object state, CancellationToken ct, TaskCreationOptions opts)
: base(_ => _a(), state, ct, opts)
{
_a = this.action;
}
protected DeferredActionTask(
Object state = null,
CancellationToken ct = default(CancellationToken),
TaskCreationOptions opts = TaskCreationOptions.None)
: this(default(Action), state, ct, opts)
{
}
protected abstract void action();
};
Для получения из 'Задания ':
public abstract class DeferredFunctionTask<TResult> : Task<TResult>
{
private DeferredFunctionTask(Func<TResult> _f, Object state, CancellationToken ct, TaskCreationOptions opts)
: base(_ => _f(), state, ct, opts)
{
_f = this.function;
}
protected DeferredFunctionTask(
Object state = null,
CancellationToken ct = default(CancellationToken),
TaskCreationOptions opts = TaskCreationOptions.None)
: this(default(Func<TResult>), state, ct, opts)
{
}
protected abstract TResult function();
};