Это нормально, чтобы получить из задачи TPL, чтобы вернуть больше деталей из метода? - PullRequest
13 голосов
/ 13 февраля 2012

Мой оригинальный метод выглядит следующим образом:

string DoSomeWork();

Метод DoSomeWork начинает некоторую работу с другим потоком и возвращает идентификатор выполнения (просто случайная строка).Позже я могу запросить результаты по заданному идентификатору выполнения.Главное, чтобы идентификатор выполнения был доступен до завершения задания.

Теперь я хочу изменить подпись, чтобы вернуть задание, чтобы пользователь мог ждать, если захочет.

Task DoSomeWork();

В то же времяМне все еще нужно вернуть идентификатор выполнения (например, для целей отслеживания), и я вижу несколько вариантов.Первый, если параметр out, второй - вернуть кортеж как с идентификатором выполнения, так и с заданием (в C # это выглядит не лучшим вариантом), и третий, о котором я действительно хочу спросить.

Что если я создам класс, который будет производным от Task class:

public class ExtendedTask : Task
{
     public string ExecutionID {get; set;}
}

Это выглядит нормально?Или лучше выбрать другие варианты?

PS В BCL есть некоторые производные от классов Task.

UPDATE , кажется, я не смог определить это ясное выражение.Но мне нужен доступ к ExecutionID до завершения задания, и поэтому я не могу использовать Task.Result.

Ответы [ 4 ]

13 голосов
/ 13 февраля 2012

Лично я бы не продлил Task<T>, я бы вместо этого сочинил .Таким образом, вам не нужно беспокоиться о API, которые возвращают только Task<T> - вы можете просто обернуть задачу.У вас может быть свойство, которое предоставляет базовую задачу, и для целей асинхронности C # 5 вы можете реализовать шаблон awaiter для вашего собственного типа - но мне кажется, что создание вашего собственного производного типа - скорее всего принесет больше вреда, чем пользы.Хотя это в основном внутреннее чувство.

Другой вариант - работать наоборот: сохранить свое дополнительное состояние в свойстве Task.AsyncState;в конце концов, для этого и есть.Таким образом, вы можете легко выполнить задачу без потери контекста выполнения, частью которого она является логически.

11 голосов
/ 13 февраля 2012

Я бы порекомендовал использовать Task<T> вместо этого, поскольку он позволяет вам «встраивать» другую информацию в результат задания.

Например, в вашем случае может иметь смысл что-то вроде:

class ExecutionResult
{
     public int ExecutionID { get; set; }
     public string Result { get; set; }
     // ...
}


public Task<ExecutionResult> DoSomeWork()
{
     return Task.Factory.StartNew( () =>
     {
          // Replace with real work, etc...
          return new ExecutionResult { ExecutionID = 0, Result = "Foo" };
     });
}

Редактировать в ответ на комментарии:

Если вам нужны данные «до» выполнения Задачи, и вы пытаетесь получить к ним доступ для других целей, я бы порекомендовал создать класс, содержащий Задачу и другие данные, и вернуть его, то есть:

class ExecutionResult
{
     public int ExecutionID { get; private set; }
     public Task<string> Result { get; private set; }
     // ... Add constructor, etc...
}


public ExecutionResult DoSomeWork()
{
     var task = Task.Factory.StartNew( () =>
     {
          // Replace with real work, etc...
          return "Foo";
     });

     return new ExecutionResult(1, task); // Make the result from the int + Task<string>
}

Это по-прежнему позволит вам получить доступ к информации о вашем процессе и Task / Task<T>.

3 голосов
/ 16 февраля 2016

Если вы делаете решите унаследовать от 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();
};
0 голосов
/ 01 декабря 2016
 private async DeferredFunctionTask<int> WaitForStart(CancellationTokenSource c, string  serviceName)
    {


        var t = await Task.Run<int>(() =>
        {
            int ret = 0;
            for (int i = 0; i < 500000000; i++)
            {


                //ret += i;
                //if (i % 100000 == 0)
                //    Console.WriteLine(i);

                if (c.IsCancellationRequested)
                {
                    return ret;
                }
            }

            return ret;

        });


        return t;
    }

Ошибка CS1983 Тип возвращаемого значения асинхронного метода должен быть void, Задача или Задача

...