Как запустить асинхронное задание объектов - PullRequest
0 голосов
/ 11 октября 2018

Я хочу запустить коллекцию из Task объектов одновременно и подождать, пока все не будет завершено.Следующий код показывает мое желаемое поведение.

public class Program
{
    class TaskTest
    {
        private Task createPauseTask(int ms)
        {
            // works well
            return Task.Run(async () =>
            // subsitution: return new Task(async () =>
            {
                Console.WriteLine($"Start {ms} ms pause");
                await Task.Delay(ms);
                Console.WriteLine($"{ms} ms are elapsed");
            });
        }

        public async Task Start()
        {
            var taskList= new List<Task>(new[]
            {
                createPauseTask(1000),
                createPauseTask(2000)
            });
            // taskList.ForEach(x => x.Start());
            await Task.WhenAll(taskList);
            Console.WriteLine("------------");
        }
    }

    public static void Main()
    {
        var t = new TaskTest();
        Task.Run(() => t.Start());
        Console.ReadKey();
    }
}

Вывод:

Start 1000 ms pause
Start 2000 ms pause
1000 ms are elapsed
2000 ms are elapsed
------------

Теперь мне интересно, можно ли подготовить мои задания и запустить их отдельно.Для этого я меняю первую строку в методе createPauseTask(int ms) с return Task.Run(async () => на return new Task(async () =>

, а в методе Start() я включил taskList.ForEach(x => x.Start()); перед WhenAll.Но тогда происходят ужасные вещи.Вызов WhenAll завершается немедленно, и вывод:

Start 2000 ms pause
Start 1000 ms pause    
------------    
1000 ms are elapsed
2000 ms are elapsed

Может кто-нибудь показать мне, в чем заключается моя ошибка и как ее исправить?

Ответы [ 2 ]

0 голосов
/ 12 октября 2018

Проблема в том, что используемый вами конструктор Task принимает делегат Action.Когда вы говорите

var task = new Task(async () => { /* Do things */ });

, вы не создаете задачу, которая «делает что-то»;Вместо этого вы создали задачу, которая выполняет действие, которое возвращает задачу , и эта (внутренняя) задача - это то, что «делает вещи».Создание этой (внутренней) задачи очень быстро, так как делегат возвращает Task в первый await и завершается почти сразу.Поскольку делегатом является Action, результирующий Task фактически отбрасывается и больше не может использоваться для ожидания.

Когда вы вызываете await Task.WhenAll(tasks) для своих внешних задач, вы толькоожидание создания внутренних задач (почти немедленных).Внутренние задачи продолжают выполняться после этого.

Существуют переопределения конструктора, которые позволяют вам делать то, что вы хотите, но ваш синтаксис будет немного более громоздким, в соответствии с ответом Пауло:

public static Task<Task> CreatePauseTask(int ms)
{
    return new Task<Task>(async () =>
        {
            Console.WriteLine($"Start {ms} ms pause");
            await Task.Delay(ms);
            Console.WriteLine($"{ms} ms are elapsed");
        });
}

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

await Task.WhenAll(await Task.WhenAll(taskList));

Внутреннее ожидание возвращает список внутренних задач, а внешнее ожидание их.


Как уже упоминалось,хотя - создание незапущенных задач с использованием конструкторов - это область, в которой вы действительно должны находиться, только если у вас есть очень специфическое требование, когда более стандартная практика использования Task.Run() или простого вызова методов, возвращающих задачу, не удовлетворяет требованию (котороебудет делать 99,99% времени).

Большинство из этих Task конструкторов были созданы еще до того, как асинхронное ожидание существовало, и, вероятно, все еще существуют по старым причинам.


Редактировать : что также может помочь, так это увидеть подписи двух делегатов, которые лямбда-нотация C # позволяет нам - обычно удобно - пропустить.

Для new Task(async () => { /* Do things */ }) у нас есть

async void Action() { }

(здесь основной проблемой является void).

Для new Task<Task>(async () => { /* Do things */ }) у нас есть

async Task Function() { }

Обе лямбды синтаксически идентичны, но семантическиу разные.

0 голосов
/ 11 октября 2018

Я не знаю, почему вы так нашли Task.Run.Каждое его использование в вашем коде просто не нужно.

Мне также интересно, где вы читали, что рекомендуется использовать конструктор Task.

В любом случае, без использования Taskконструктор, будет что-то вроде этого:

class TaskTest
{
    private async Task CreatePauseTask(int ms)
    {
        Console.WriteLine($"Start {ms} ms pause");
        await Task.Delay(ms);
        Console.WriteLine($"{ms} ms are elapsed");
    }

    public async Task Start()
    {
        var taskList = new List<Task>(new[]
        {
                CreatePauseTask(1000),
                CreatePauseTask(2000)
            });
        await Task.WhenAll(taskList);
        Console.WriteLine("------------");
    }
}


static void Main()
{
    var t = new TaskTest();
    t.Start();
    Console.ReadKey();
}

И это выводит:

Start 1000 ms pause
Start 2000 ms pause
1000 ms are elapsed
2000 ms are elapsed
------------

И использование конструктора Task будет:

class TaskTest
{
    private Task CreatePauseTask(int ms)
    {
        return new Task<Task>(async () =>
        {
            Console.WriteLine($"Start {ms} ms pause");
            await Task.Delay(ms);
            Console.WriteLine($"{ms} ms are elapsed");
        }, TaskCreationOptions.DenyChildAttach);
    }

    public async Task Start()
    {
        var taskList = new List<Task>(new[]
        {
                CreatePauseTask(1000),
                CreatePauseTask(2000)
            });
        taskList.ForEach(t => t.Start(TaskScheduler.Default));
        await Task.WhenAll(taskList);
        Console.WriteLine("------------");
    }
}


static void Main()
{
    var t = new TaskTest();
    t.Start();
    Console.ReadKey();
}

Ивыводит:

Start 1000 ms pause
Start 2000 ms pause
------------
1000 ms are elapsed
2000 ms are elapsed

Проблема здесь в том, что Task.Run понимает Task возвращающие функции, а конструктор Task - нет.Таким образом, использование конструктора Task вызовет функцию, которая вернется при первом ожидании блокировки (await Task.Delay(ms);).

Это ожидаемое поведение.

...