Cualquier diferencia entre "await Task.Run(); return;" y "devolver Task.Run()"? [duplicar]

Resuelto avo asked hace 10 años • 4 respuestas

¿Existe alguna diferencia conceptual entre los siguientes dos fragmentos de código?

async Task TestAsync() 
{
    await Task.Run(() => DoSomeWork());
}

y

Task TestAsync() 
{
    return Task.Run(() => DoSomeWork());
}

¿El código generado tampoco difiere?

EDITAR: Para evitar confusiones con Task.Runun caso similar:

async Task TestAsync() 
{
    await Task.Delay(1000);
}

y

Task TestAsync() 
{
    return Task.Delay(1000);
}

ACTUALIZACIÓN ÚLTIMA: Además de la respuesta aceptada, también hay una diferencia en cómo LocalCallContextse maneja: CallContext.LogicalGetData se restaura incluso cuando no hay asincronía. ¿Por qué?

avo avatar Jan 10 '14 06:01 avo
Aceptado

Una diferencia importante está en la propagación de excepciones. Una excepción, lanzada dentro de un método async Task, se almacena en el Taskobjeto devuelto y permanece inactiva hasta que se observa la tarea mediante await task, o . Se propaga de esta manera incluso si se lanza desde la parte síncrona del método.task.Wait()task.Resulttask.GetAwaiter().GetResult()async

Considere el siguiente código, donde OneTestAsyncy AnotherTestAsyncse comportan de manera bastante diferente:

static async Task OneTestAsync(int n)
{
    await Task.Delay(n);
}

static Task AnotherTestAsync(int n)
{
    return Task.Delay(n);
}

// call DoTestAsync with either OneTestAsync or AnotherTestAsync as whatTest
static void DoTestAsync(Func<int, Task> whatTest, int n)
{
    Task task = null;
    try
    {
        // start the task
        task = whatTest(n);

        // do some other stuff, 
        // while the task is pending
        Console.Write("Press enter to continue");
        Console.ReadLine();
        task.Wait();
    }
    catch (Exception ex)
    {
        Console.Write("Error: " + ex.Message);
    }
}

Si llamo DoTestAsync(OneTestAsync, -2), produce el siguiente resultado:

Presione enter para continuar
Error: se produjeron uno o más errores. Await Task.Delay
Error: 2do

Nota, tuve que presionar Enterpara verlo.

Ahora, si llamo DoTestAsync(AnotherTestAsync, -2), el flujo de trabajo del código interno DoTestAsynces bastante diferente, al igual que el resultado. Esta vez, no me pidieron que presionara Enter:

Error: el valor debe ser -1 (lo que significa un tiempo de espera infinito), 0 o un número entero positivo.
Nombre del parámetro: milisegundosDelayError: 1.º

En ambos casos Task.Delay(-2)se lanza al principio, mientras se validan sus parámetros. Este podría ser un escenario inventado, pero en teoría Task.Delay(1000)también puede ocurrir, por ejemplo, cuando falla la API del temporizador del sistema subyacente.

Como nota al margen, la lógica de propagación de errores es aún diferente para async voidlos métodos (a diferencia de async Tasklos métodos). Una excepción generada dentro de un async voidmétodo se volverá a lanzar inmediatamente en el contexto de sincronización del hilo actual (a través de SynchronizationContext.Post), si el hilo actual tiene uno ( SynchronizationContext.Current != null). De lo contrario, se volverá a lanzar a través de ThreadPool.QueueUserWorkItem). La persona que llama no tiene la posibilidad de controlar esta excepción en el mismo marco de pila.

Publiqué más detalles sobre el comportamiento de manejo de excepciones de TPL aquí y aquí .


P : ¿Es posible imitar el comportamiento de propagación de excepciones de asyncmétodos para métodos no asíncronos Task, de modo que estos últimos no se arrojen en el mismo marco de pila?

R : Si realmente es necesario, entonces sí, hay un truco para ello:

// async
async Task<int> MethodAsync(int arg)
{
    if (arg < 0)
        throw new ArgumentException("arg");
    // ...
    return 42 + arg;
}

// non-async
Task<int> MethodAsync(int arg)
{
    var task = new Task<int>(() => 
    {
        if (arg < 0)
            throw new ArgumentException("arg");
        // ...
        return 42 + arg;
    });

    task.RunSynchronously(TaskScheduler.Default);
    return task;
}

Sin embargo, tenga en cuenta que, bajo ciertas condiciones (como cuando está demasiado profundo en la pila), RunSynchronouslyaún podría ejecutarse de forma asincrónica.


Otra diferencia notable es que la versión async/ awaites más propensa a bloquearse en un contexto de sincronización no predeterminado . Por ejemplo, lo siguiente se bloqueará en una aplicación WinForms o WPF:

static async Task TestAsync()
{
    await Task.Delay(1000);
}

void Form_Load(object sender, EventArgs e)
{
    TestAsync().Wait(); // dead-lock here
}

Cámbielo a una versión no asíncrona y no se bloqueará:

Task TestAsync() 
{
    return Task.Delay(1000);
}

Stephen Cleary explica bien la naturaleza del punto muerto en su blog .

noseratio avatar Jan 13 '2014 01:01 noseratio

Cuál es la diferencia entre

async Task TestAsync() 
{
    await Task.Delay(1000);
}

y

Task TestAsync() 
{
    return Task.Delay(1000);
}

?

Estoy confundido por esta pregunta. Déjame intentar aclarar respondiendo a tu pregunta con otra pregunta. ¿Cuál es la diferencia entre?

Func<int> MakeFunction()
{
    Func<int> f = ()=>1;
    return ()=>f();
}

y

Func<int> MakeFunction()
{
    return ()=>1;
}

?

Cualquiera que sea la diferencia entre mis dos cosas, la misma diferencia hay entre tus dos cosas.

Eric Lippert avatar Jan 10 '2014 00:01 Eric Lippert
  1. El primer método ni siquiera se compila.

    Dado que " Program.TestAsync()" es un método asíncrono que devuelve " Task", una palabra clave de retorno no debe ir seguida de una expresión de objeto. ¿ Tenías intención de regresar ' Task<T>'?

    Tiene que ser

    async Task TestAsync()
    {
        await Task.Run(() => DoSomeWork());
    }
    
  2. Existe una gran diferencia conceptual entre estos dos. El primero es asincrónico, el segundo no. Lea Rendimiento asíncrono: comprensión de los costos de Async y espere para conocer un poco más sobre los aspectos internos de async/ await.

  3. Generan código diferente.

    .method private hidebysig 
        instance class [mscorlib]System.Threading.Tasks.Task TestAsync () cil managed 
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = (
            01 00 25 53 4f 54 65 73 74 50 72 6f 6a 65 63 74
            2e 50 72 6f 67 72 61 6d 2b 3c 54 65 73 74 41 73
            79 6e 63 3e 64 5f 5f 31 00 00
        )
        .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x216c
        // Code size 62 (0x3e)
        .maxstack 2
        .locals init (
            [0] valuetype SOTestProject.Program/'<TestAsync>d__1',
            [1] class [mscorlib]System.Threading.Tasks.Task,
            [2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
        )
    
        IL_0000: ldloca.s 0
        IL_0002: ldarg.0
        IL_0003: stfld class SOTestProject.Program SOTestProject.Program/'<TestAsync>d__1'::'<>4__this'
        IL_0008: ldloca.s 0
        IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create()
        IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder'
        IL_0014: ldloca.s 0
        IL_0016: ldc.i4.m1
        IL_0017: stfld int32 SOTestProject.Program/'<TestAsync>d__1'::'<>1__state'
        IL_001c: ldloca.s 0
        IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder'
        IL_0023: stloc.2
        IL_0024: ldloca.s 2
        IL_0026: ldloca.s 0
        IL_0028: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<valuetype SOTestProject.Program/'<TestAsync>d__1'>(!!0&)
        IL_002d: ldloca.s 0
        IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder'
        IL_0034: call instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task()
        IL_0039: stloc.1
        IL_003a: br.s IL_003c
    
        IL_003c: ldloc.1
        IL_003d: ret
    } // end of method Program::TestAsync
    

    y

    .method private hidebysig 
        instance class [mscorlib]System.Threading.Tasks.Task TestAsync2 () cil managed 
    {
        // Method begins at RVA 0x21d8
        // Code size 23 (0x17)
        .maxstack 2
        .locals init (
            [0] class [mscorlib]System.Threading.Tasks.Task CS$1$0000
        )
    
        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: ldftn instance class [mscorlib]System.Threading.Tasks.Task SOTestProject.Program::'<TestAsync2>b__4'()
        IL_0008: newobj instance void class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>::.ctor(object, native int)
        IL_000d: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>)
        IL_0012: stloc.0
        IL_0013: br.s IL_0015
    
        IL_0015: ldloc.0
        IL_0016: ret
    } // end of method Program::TestAsync2
    
MarcinJuraszek avatar Jan 09 '2014 23:01 MarcinJuraszek