54

Вопрос по generics, c# – Существует ли универсальный Task.WaitAll?

Я начинаю несколько параллельных задач, например:

var tasks =
    Enumerable.Range(1, 500)
    .Select(i => Task.Factory.StartNew<int>(ProduceSomeMagicIntValue))
    .ToArray();

а затем присоединиться к ним с

Task.WaitAll(tasks);

На этой последней строке я получаю синий волнистый маркер подtasksс предупреждением:

Co-variant array conversion from Task[] to Task[] 
can cause run-time exception on write operation.

Я понимаю, почему я получаю это сообщение, но есть ли способ обойти это? (например, как общая версияTask.WaitAll()?)

  • «Фактическая реализация» это довольно нетривиально. Не говоря уже о том, что вы теряете (или нуждаетесь в перекодировании), если в реализации .NET Framework есть исправления ошибок или улучшения производительности. Решения от @MerickOWA и DMac the Destroyer (в таком порядке) решают проблему OP очень просто.

    от
  • @ W0lf, затем просто сделайте дополнительный .ToArray & lt; Task & gt; (), прежде чем передать его в Task.WaitAll, любое возможное назначение этой скопированной Task [] не может вызвать исключение во время выполнения.

    от
  • +1, это возможно, но в моем случае я фактически использую массив, чтобы получитьint Результаты:var results = tasks.Select(task => task.Result)

    от Cristian Lupascu
  • @MarkAmery это предполагает, что задачи уже отправляются, когда они передаются в этот метод.

    от
  • Если люди настаивают на создании своих собственных (вместо использования одного из других решений, которые преобразуют универсальную коллекцию в массив Task), это лучший источник, с полными комментариями и ссылками на весь внутренний код, который вам также понадобится дубликат, чтобы он работал:referencesource.microsoft.com/#mscorlib/system/threading/Tasks/…

    от
  • Это отлично сработало для меня. Протестировал его как с задачами, которые выполнялись до конца, так и с задачами, которые истекли по времени, используяvar ifAllTasksRanToCompletion = Task.WaitAll(tasks.Cast<Task>().ToArray(), timeout: TimeSpan.FromSeconds(20));.

    от
  • Это действительно явно неправильно - вы выполняете свои задачиserially здесь, который не является реальнымWaitAll делает.

    от
  • есть ли шанс, что вы примете другой ответ? Лично я нашел других, более достойных, чем тот, который вы выбрали в качестве "принятого".

    от Quibblesome
  • @svick спасибо за совет. Похоже, они переименовали то, о чем вы говорите, в «Когда все», так что вы можете просто сказать «ожидайте Task.WhenAll (task1, task2);»

    от Simon_Weaver
  • В этом случае преобразование безопасно, потому чтоWaitAll() не будет писать в массив. Есть ли причина, по которой вы хотите этого избежать?

    от svick
  • Также .Net 4.5 будет содержатьTask.WhenAll() который возвращает одинTask что завершается, когда всеTaskы в коллекции завершены. И у этого также есть универсальная версия, и это работает на любомIEnumerable<T> изTasks.

    от svick
  • @ Достаточно готово.

    от Cristian Lupascu
  • 1

    На самом деле там Task all = Task.WhenAll(tasks)

    A DIFFENENT ANSWER

    На самом деле тамIS похожая общая перегрузка:

    Task all = Task.WhenAll(tasks)
    

    Это отличается в этом он возвращаетTask это будет завершено после выполнения всех задач. так что вы можете использоватьawait на нем илиWait(), что вы хотите.

    Посмотрите на подпись:

    Overloads

    --------- NON GENERIC OVERLOADS --------------

    WhenAll(IEnumerable<Task>) Creates a task that will complete when all of the Task objects in an enumerable collection have completed.

    WhenAll(Task[]) Creates a task that will complete when all of the Task objects in an array have completed.

    --------- GENERIC OVERLOADS --------------

    WhenAll<TResult>(IEnumerable<Task<TResult>>) Creates a task that will complete when all of the Task<TResult> objects in an enumerable collection have completed.

    WhenAll<TResult>(Task<TResult>[]) Creates a task that will complete when all of the Task<TResult> objects in an array have completed.

  • 26

    Я уверен

    что это безопасная операция даже с предупреждением, но если вы действительно хотите обойти это, лучшим вариантом, чем создание собственной реализации, было бы просто преобразовать вашуtasks параметр в тип, который он хочет:

    Task.WaitAll(tasks.Cast<Task>().ToArray())
    

    Это убивает синие закорючки для меня, позволяет мне сохранить мойtasks Переменная generic и не вынуждает меня создавать много нового страшного кода, который в конечном счете не нужен.

  • 7

    Вы можете создать метод расширения для этого.

    Я не знаю точную реализацию WaitAll, но мы можем предположить, что он ждет завершения каждого элемента:

    static class TaskExtensions
    {
        public static void WaitAll<T>(this Task<T>[] tasks)
        {
            foreach (var item in tasks)
            {
                item.Wait();
            }
        }
    }
    

    Затем позвоните, из вашего текущего кода:

    tasks.WaitAll();
    

    Edit

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

    http://pastebin.com/u30PmrdS

    Вы можете изменить это для поддержки общих задач.

  • 25

    Универсальный метод Task.WaitAll подразумевает

    что все Задачи должны будут возвращать один и тот же тип, что будет крайне ограничено. Написание чего-то подобного можно было бы сделать вручную (см. Ответ Бас Брекелманс), но это не позволит выполнять ContinueWith или отмену без большого труда.

    Простое решение, если вы не используете массив для чего-либо еще, это

      .ToArray<Task>();