No se puede llamar al inicio en una tarea de estilo promesa. se acerca la excepción

108

Estoy creando una aplicación de escritorio wpf simple. La interfaz de usuario tiene solo un botón y un código en un archivo .cs como.

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    FunctionA();
}

public void FunctionA()
{
    Task.Delay(5000).Start();
    MessageBox.Show("Waiting Complete");
}

Pero, sorprendentemente, la línea Task.Delay(5000).Start();está lanzando un InvalidOperationException:

No se puede llamar al inicio en una tarea de estilo promesa.

¿Alguien puede ayudar por qué es así?

DJ
fuente

Respuestas:

171

Recibes ese error porque la Taskclase ya inició la tarea antes de dártela. Solo debe llamar Starta una tarea que crea llamando a su constructor, y ni siquiera debe hacerlo a menos que tenga una razón convincente para no iniciar la tarea cuando la cree; si desea que comience de inmediato, debe usar Task.Runo Task.Factory.StartNewpara crear y comenzar uno nuevo Task.

Entonces, ahora sabemos simplemente deshacernos de ese molesto Start. Ejecutará su código y encontrará que el cuadro de mensaje se muestra de inmediato, no 5 segundos después, ¿qué pasa con eso?

Bueno, Task.Delaysolo te da una tarea que se completará en 5 segundos. No detiene la ejecución del hilo durante 5 segundos. Lo que quiere hacer es tener un código que se ejecute después de que finalice la tarea. Para eso ContinueWithes. Le permite ejecutar algún código después de que se realiza una tarea determinada:

public void FunctionA()
{
    Task.Delay(5000)
    .ContinueWith(t => 
    {
        MessageBox.Show("Waiting Complete");
    });
}

Esto se comportará como se esperaba.

También podríamos aprovechar la awaitpalabra clave de C # 5.0 para agregar continuaciones más fácilmente:

public async Task FunctionA()
{
    await Task.Delay(5000);
    MessageBox.Show("Waiting Complete");
}

Si bien una explicación completa de lo que está sucediendo aquí está más allá del alcance de esta pregunta, el resultado final es un método que se comporta de manera muy similar al método anterior; mostrará un cuadro de mensaje 5 segundos después de llamar al método, pero el método en sí regresará [casi] inmediatamente en ambos casos. Dicho esto, awaites muy poderoso y nos permite escribir métodos que parecen simples y directos, pero que serían mucho más difíciles y complicados de escribir usando ContinueWithdirectamente. También simplifica enormemente el manejo de errores, eliminando una gran cantidad de código repetitivo.

Servy
fuente
1

Prueba esto.

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    FunctionA();
}

public async void FunctionA()
{
    await Task.Delay(5000);
    MessageBox.Show("Waiting Complete");
}
Mathias Lykkegaard Lorenzen
fuente
-4

Como dijo Servy, la tarea ya ha comenzado, así que todo lo que te queda por hacer es esperarla (.Wait ()):

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    FunctionA();
}
public void FunctionA()
{
    Task.Delay(5000).Wait();
    MessageBox.Show("Waiting Complete");
}
Sergiu
fuente
1
Llamar Wait()a una tarea bloqueará el hilo actual hasta que la tarea se resuelva. Eso casi nunca es lo que quieres que suceda.
Jeremy
1
@Jeremy: De hecho, vale la pena prestar atención al comportamiento que mencionaste, pero en este caso su FunctionA ya estaba bloqueando el hilo actual, así que asumí que solo está buscando una manera de determinar cuándo se completó la tarea. Para aclarar la diferencia entre Wait y async (para futuros lectores), lea este enlace
Sergiu