68 votos

¿Existe un Task.WaitAll genérico?

Empiezo algunas tareas paralelas, así:

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

y luego unirlos con

Task.WaitAll(tasks);

En esta última línea me sale un garabato azul debajo de tasks con un mensaje de advertencia:

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

Entiendo por qué recibo este mensaje, pero ¿hay alguna forma de evitarlo? (por ejemplo, como una versión genérica de Task.WaitAll() ?)

3 votos

En este caso, la conversión es segura, porque WaitAll() no escribirá en la matriz. ¿Hay alguna razón por la que quiera evitarlo?

17 votos

Además, .Net 4.5 contendrá Task.WhenAll() que devuelve un único Task que se completa cuando todos los Task s de la colección completa. Y también tiene una versión genérica y funciona en cualquier IEnumerable<T> de Task s.

0 votos

@svick gracias por el consejo. parece que han cambiado el nombre de lo que estás hablando a WhenAll por lo que sólo puede decir 'await Task.WhenAll(task1, task2);'

31voto

MerickOWA Puntos 4310

Un método genérico de Task.WaitAll implicaría que todas las Tareas tendrían que devolver el mismo tipo, lo que tendría una utilidad extremadamente limitada. Escribir algo así podría hacerse manualmente (ver la respuesta de Bas Brekelmans), pero esto no permitiría ContinueWith o cancelación sin mucho trabajo.

Una solución sencilla si no está utilizando la matriz para otra cosa es

  .ToArray<Task>();

0 votos

+1 esa es una posibilidad, pero en mi caso, en realidad uso el Array para obtener el int resultados: var results = tasks.Select(task => task.Result)

1 votos

@W0lf entonces simplemente haz un extra .ToArray<Task>() antes de pasarlo a Task.WaitAll, cualquier posible asignación a esta Task[] copiada no puede causar una excepción en tiempo de ejecución

29voto

DMac the Destroyer Puntos 1673

Estoy bastante seguro de que es una operación segura, incluso con la advertencia, pero si realmente quería conseguir alrededor de una mejor opción que la creación de su propia aplicación sería sólo para convertir su tasks en el tipo que desee:

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

Eso mata a los garabatos azules para mí, me permite mantener mi tasks variable genérica y no me obliga a crear un montón de nuevo código espantoso que al final es innecesario.

1 votos

A mí me ha funcionado perfectamente. Lo probé tanto con tareas que se ejecutaban hasta su finalización como con tareas que se agotaban en el tiempo, utilizando var ifAllTasksRanToCompletion = Task.WaitAll(tasks.Cast<Task>().ToArray(), timeout: TimeSpan.FromSeconds(20)); .

21voto

Yitzchak Puntos 1256

UNA RESPUESTA MEJOR Y MÁS SENCILLA

En realidad hay IS una sobrecarga genérica similar:

Task all = Task.WhenAll(tasks)

Se diferencia en que devuelve un Task que se completará una vez finalizadas todas las tareas, por lo que puede utilizar await en él, o Wait() lo que quieras.

Mira la firma:

Sobrecargas

--------- SOBRECARGAS NO GENÉRICAS --------------

WhenAll(IEnumerable<Task>) Crea una tarea que completará cuando todos los Task o completado.

WhenAll(Task[]) Crea una tarea que co Task objetos de una Matriz se han completado.

--------- SOBRECARGAS GENÉRICAS --------------

WhenAll<TResult>(IEnumerable<Task<TResult>>) C completará cuando todos los Task<TResult> o se han completado.

WhenAll<TResult>(Task<TResult>[]) Crea una tarea que completará cuando todos los Task<TResult> objetos de una Matriz se han completado.

6voto

Bas Brekelmans Puntos 13799

Para ello, puede crear un método de extensión.

No conozco la implementación exacta de WaitAll, pero podemos asumir que espera a que se complete cada elemento:

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

Luego llama, desde tu código actual:

tasks.WaitAll();

Editar

La aplicación real es un poco más compleja. He omitido el código de esta respuesta porque es bastante largo.

http://pastebin.com/u30PmrdS

Puede modificarlo para que admita tareas genéricas.

2 votos

La "implementación real" no es trivial. Por no mencionar que pierdes (o necesitas recodificar) si la implementación de .NET Framework tiene alguna corrección de errores o mejoras de rendimiento. Las soluciones de @MerickOWA y DMac the Destroyer (en ese orden) resuelven el problema del OP de forma muy sencilla.

1 votos

Esto es obviamente incorrecto - estás ejecutando tus tareas en serie aquí, que no es lo que el verdadero WaitAll lo hace.

1 votos

@MarkAmery esto asume que las tareas ya están despachadas cuando se introducen en este método.

0voto

Un enfoque un poco más granular. A mí personalmente me gusta el Array de tareas... es sólo mi preferencia :-)

try
{
    Object sync = new Object();

    CancellationToken token = cts.Token;
    taskArray[i] = Task.Factory.StartNew( () => 
    {
        lock (sync)
        {
            pcq.DoWork(fh);
        }
    }, token);

    bool Completed = Task.WaitAll(taskArray, 60000, token);
    // if still not processed, this row is pretty much not going to happen
    if (Completed == false)
    {

        if (taskArray[i].IsCanceled == false)
        {
            // kill thread
            cts.Cancel();
            // dispose token
            if (cts != null) { cts.Dispose(); }

        }

    }

}
catch (Exception ex)
{}

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X