C # Совокупность задач - PullRequest
       8

C # Совокупность задач

0 голосов
/ 05 декабря 2018

Допустим, у меня есть несколько задач, и я хочу выполнить их одну за другой с задержкой между ними.У меня была идея сложить их с помощью Aggregate в одну задачу, объединив с ContinueWith и вставив Task.Delay() между каждой парой:

var tasks = new[] {1, 2, 3, 4, 5}.Select(async x => Console.WriteLine(x));
var superTask =
    tasks.Aggregate(Task.CompletedTask, async (task1, task2) =>
        await (await task1.ContinueWith(_ => Task.Delay(1000))).ContinueWith(_ => task2));

await superTask;

Вопрос в том, почему это не работает?

Ответы [ 2 ]

0 голосов
/ 05 декабря 2018

Вы писали:

Я хочу выполнить их один за другим с задержкой между ними.

Есть несколько проблем с вашим кодом.

В вашем примере вы создали перечислимое перечисление, не перечисляя его.Поэтому ваши задачи еще не начались.Они начнутся, как только вы начнете перечислять.Поэтому, как только вы используете foreach, или ToList(), или перечисление низкого уровня: GetEnumerator() и MoveNext().

var tasks = new[] {1, 2, 3, 4, 5}.Select(async x => Console.WriteLine(x))
     .ToList();

Каждая задача запускается сразу после ее создания, поэтому теперь они выполняются одновременно.

Ваша агрегатная функция также перечисляет каждый элемент по одному и выполняет ContinueWith.Перечисление уже запускает задачу.

Посмотрите на исходный код Enumerable. Агрегат

public static TSource Aggregate<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, TSource, TSource> func)
    {
        ... // some test for null
        using (IEnumerator<TSource> e = source.GetEnumerator())
        {
            if (!e.MoveNext()) throw Error.NoElements(); // there must be at least one element

            TSource result = e.Current;
            while (e.MoveNext()) result = func(result, e.Current);
            return result;
        }
    }

Итак, что он делает:

  • Первый MoveNext() выполняет ваш оператор Select один раз.Первая задача создана и должна быть запущена как можно скорее, возможно, немедленно.
  • e.Current используется для сохранения этой переменной текущей задачи Result.
  • 2-й MoveNext() снова выполняет оператор Select.Вторая задача создана и запланирована к запуску как можно скорее, возможно сразу.
  • e.Current содержит вашу вторую задачу.Используется для выполнения func(result, e.Current)

Эта функция будет делать следующее:

<first task>.ContinueWith(delay task).ContinueWith(<2ndTask>).

Результат помещается в переменный результат, MoveNext выполняется и т. д.

Помните: 1-я задача и 2-я задача уже запущены!

Так что на самом деле ваш агрегат выглядит примерно так:

Task result = empty task
for every task
{
     Create it and schedule it to start running as soon as possible
     Result = Result.ContinueWith(delayTask).ContinueWith(already running created task)
}

Теперь, что произойдет, если вы запуститезадача, и ContinueWith еще одна уже запущенная задача?

Если вы посмотрите на Task.ContinueWith , он говорит, что параметр continuationAction ContinueWith означает:

Действие, запускаемое по завершении задачи.

Что произойдет, если вы запустите задачу, которая уже выполняется?Я не знаю, какой-нибудь тестовый код даст вам ответ.Мое лучшее предположение, что это ничего не сделаетЭто, конечно, не остановит уже запущенную задачу.

Это не то, что вы хотите!

Мне кажется, что это не то, что вы хотите.Вы хотите указать некоторые действия для последовательного выполнения с задержкой между ними.Примерно так:

Create a Task for Action 1, and wait until finished
Delay
Create a Task for Action 2, and wait until finished
Delay
...

Итак, вам нужна функция с вводом последовательности Actions и DelayTime.Каждое действие будет запущено с ожиданием до его завершения, после чего ожидается DelayTime.

Вы можете сделать это с помощью Aggregate, где на входе есть последовательность действий.Результат будет ужасным.Трудно читать, тестировать и поддерживать.Процедура будет намного проще для понимания.

Чтобы сделать ее совместимой с LINQ, я создам метод расширения.См. демистифицированные методы расширения

static Task PerformWithDelay(this IEnumerable<Action> actionsToPerform, TimeSpan delayTime)
{
    var actionEnumerator = actionsToPerform.GetEnumerator();

    // do nothing if no actions to perform
    if (!actionEnumerator.MoveNext())
        return Task.CompletedTask;
    else
    {    // Current points to the first action
         Task result = actionEnumerator.Current;

         // enumerate over all other actions:
         while (actionEnumerator.MoveNext())
         {
             // there is a next action. ContinueWith delay and next task
             result.ContinueWith(Task.Delay(delayTime)
                   .ContinueWith(Task.Run( () => actionEnumerator.Current);
         } 
     }
}

Ну, если вы действительно, действительно хотите использовать агрегат, чтобы произвести впечатление на ваших коллег:

Task resultTask = actionsToPerform.Aggregate<Action, Task> (
    action =>
    {
         previousResultTask.ContinueWith(Task.Delay(delayTime))
                           .ContinueWith(Task.Run( () => action));
     });

Итак, мы собираем агрегациюс первым элементом вашей последовательности для каждого действия в вашей последовательности действий: ContinueWith нового Task.Delay и ContinueWith нового Task, который выполняет действие.

Проблема: исключение, если ваш ввод пуст.

0 голосов
/ 05 декабря 2018

Есть пара проблем.Прежде всего, вы не ожидаете задержки, поэтому она может даже не существовать.Во-вторых, задачи будут автоматически запускаться сразу при вызове таким образом - вместо этого вам нужно использовать метод конструктора (см. этот вопрос ).

Этот фрагмент кода будет делать то, что вы хотите.

var tasks = new[] { 1, 2, 3, 4, 5 }
    .Select
    (
        x => new Task( () => Console.WriteLine(x) )
    );
var superTask =
    tasks.Aggregate
    (
        Task.CompletedTask,
        async (task1, task2) =>
        {
            await task1;
            await Task.Delay(1000);
            task2.Start();
        }
    );
await superTask;

Несмотря на это, я очень сомневаюсь, что это правильный подход.Просто напишите простой цикл для итерации по массиву и работайте с ними по одному;асинхронность даже не нужна.

...