Подтвердить что ты не робот

Является ли это нормально для TPL Task, чтобы возвращать более подробную информацию из метода?

Мой оригинальный метод выглядит так:

string DoSomeWork();

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

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

Task DoSomeWork();

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

Я бы рекомендовал вместо этого использовать 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" };
     });
}

Изменить в ответ на комментарии:

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

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

Если вы решили наследовать от Task или Task<TResult>, вы можете столкнуться с разочарованием, что делегат Action<Object> или Func<Object,TResult>, который обеспечивает фактическую работу для задачи , должен быть указан в момент создания объекта, созданного Task, и не может быть изменен позже. Это верно, даже если конструктор базового класса не выполняет Start() вновь созданную задачу, и на самом деле она может быть запущена не позже, чем когда-либо вообще.

Это затрудняет использование класса Task -derived в ситуациях, когда экземпляры должны быть созданы до того, как будут указаны все подробные сведения о его возможной работе.

Примером может быть аморфная сеть известных узлов Task<TResult>, работающих с общей целью, так что они могут осуществлять доступ друг к другу Result. Самый простой способ гарантировать, что вы можете Wait() на любом произвольном node в сети, предварительно подготовить все их до начала любого из них. Это полностью устраняет проблему попыток анализа зависимостей рабочего графика и позволяет факторам времени выполнения определять, когда, если и в каком порядке требуются значения Result.

Проблема здесь в том, что для некоторых узлов вы не сможете предоставить функцию, определяющую работу во время построения. Если для создания необходимой лямбда-функции требуется закрыть значения Result из других задач в сети, Task<TResult>, который предоставляет Result, который мы хотим, возможно, еще не был создан. И даже если это происходит раньше, на этапе предварительного строительства, вы не можете называть его Start(), поскольку он может включать зависимости от других узлов, которые этого не сделали. Помните, что цель предварительного построения сети заключалась в том, чтобы избежать таких сложностей.

Как будто этого было недостаточно, есть и другие причины, из-за которых неудобно использовать лямбда-функцию для обеспечения желаемой функции. Поскольку он передается конструктору в качестве аргумента, функция не может получить доступ к указателю this конечного экземпляра задачи, что делает уродливый код, особенно учитывая, что лямбда обязательно определяется в рамках - и, возможно, непреднамеренного закрытия over - некоторый не связанный указатель this.

Я мог бы продолжить, но суть в том, что при определении расширенной функциональности в производном классе вам не нужно выносить раздувание при запуске и другие проблемы. Разве это не пропустит всю точку полиморфизма? Было бы более элегантно определить рабочий делегат класса Task -derived обычным способом, а именно абстрактную функцию в базовом классе.

Вот как это сделать. Трюк состоит в том, чтобы определить частный конструктор, который закрывает один из своих собственных аргументов. Аргумент, первоначально установленный в 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();
};

Вывод из "Задачи <TResult> ":

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

[Изменить: Упрощен]

Эти упрощенные версии дополнительно уменьшают посторонние замыкания, закрывая непосредственно по методу действие производных экземпляров > . Это также освобождает 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();
};

Вывод из "Задачи <TResult> ":

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

Ответ 4

 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 Тип возврата метода async должен быть недействительным, Задача или Задача