“await Task.Run(); return;”之间的任何区别并“返回Task.Run()”?

时间:2014-01-09 23:11:16

标签: c# async-await

以下两段代码之间是否存在任何概念上的差异:

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

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

生成的代码是否有所不同?

编辑:为避免与Task.Run混淆,类似案例:

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

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

迟到更新:除了接受的答案外,处理LocalCallContext的方式也有所不同:CallContext.LogicalGetData gets restored even where there is no asynchrony. Why?

4 个答案:

答案 0 :(得分:70)

更新,除了下面解释的异常传播行为的差异外,还有另一个微妙的区别:async / await版本更容易死锁非默认同步上下文。例如,以下内容将在WinForms或WPF应用程序中死锁:

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

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

将其更改为非异步版本,它不会死锁:

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

Stephen Cleary在他的blog中很好地解释了死锁的本质。

<小时/> 另一个主要区别在于异常传播。async Task方法中抛出的异常存储在返回的Task对象中并保持休眠状态,直到通过{{ 1}},await tasktask.Wait()task.Result。即使从task.GetAwaiter().GetResult()方法的同步部分抛出,它也会以这种方式传播。

请考虑以下代码,其中asyncOneTestAsync的行为完全相同:

AnotherTestAsync

如果我拨打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); } } ,它会产生以下输出:

Press enter to continue
Error: One or more errors occurred.await Task.Delay
Error: 2nd

注意,我必须按 Enter 才能看到它。

现在,如果我调用DoTestAsync(OneTestAsync, -2)DoTestAsync(AnotherTestAsync, -2)内的代码工作流程就完全不同了,输出也是如此。这一次,我没有被要求按 Enter

Error: The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer.
Parameter name: millisecondsDelayError: 1st

在两种情况下,DoTestAsync在开始时抛出,同时验证其参数。这可能是一个虚构的场景,但理论上Task.Delay(-2)也可能抛出,例如,当底层系统计时器API失败时。

另外, Task.Delay(1000)方法(与async void方法相反)的错误传播逻辑不同。如果当前线程有一个(async Task,则async void方法内引发的异常将立即重新抛出当前线程的同步上下文(通过SynchronizationContext.Post)。否则,它将会通过SynchronizationContext.Current != null)重新抛出。调用者没有机会在同一堆栈帧上处理此异常。

我发布了有关TPL异常处理行为herehere的更多详细信息。


Q :是否可以模仿基于非异步ThreadPool.QueueUserWorkItem的方法的async方法的异常传播行为,以便后者不会抛出相同的堆栈框架?

A :如果真的需要,那么是的,有一个技巧:

Task

但请注意,under certain conditions(就像它在堆栈上太深时),// 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; } 仍然可以异步执行。

答案 1 :(得分:52)

  

之间有什么区别
async Task TestAsync() 
{
    await Task.Delay(1000);
}
     

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

我对这个问题很困惑。让我试着用另一个问题回答你的问题来澄清。有什么区别?

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

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

无论我的两件事之间有什么不同,你的两件事之间的区别也是一样。

答案 2 :(得分:9)

  1. 第一种方法甚至不编译。

      

    由于“Program.TestAsync()”是一个返回“Task”的异步方法,因此返回关键字后面不能包含对象表达式。您打算退回“Task<T>”吗?

    必须是

    async Task TestAsync()
    {
        await Task.Run(() => DoSomeWork());
    }
    
  2. 这两者之间存在重大的概念差异。第一个是异步的,第二个不是。 Read Async Performance: Understanding the Costs of Async and Await了解async / await的内部信息。

  3. 他们会生成不同的代码。

    .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
    

    .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
    

答案 3 :(得分:8)

两个示例不同。当方法用async关键字标记时,编译器会在后台生成状态机。一旦等待等待,这就是继续恢复延续的原因。

相反,当某个方法 标有async时,您正在失去await等待的能力。 (也就是说,在方法本身内;该方法仍然可以被其调用者等待。)但是,通过避免async关键字,您不再生成状态机,这可能会增加一些开销(将本地提升到状态机的字段,将其他对象提升到GC)。

在这样的示例中,如果您能够避免async-await并直接返回等待,则应该这样做以提高方法的效率。

请参阅this questionthis answer,这些问题与您的问题和答案非常相似。