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

avo*_*avo 77 c# async-await

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

async Task TestAsync() 
{
    await Task.Run(() => DoSomeWork());
}
Run Code Online (Sandbox Code Playgroud)

Task TestAsync() 
{
    return Task.Run(() => DoSomeWork());
}
Run Code Online (Sandbox Code Playgroud)

生成的代码也不同吗?

编辑:为避免混淆Task.Run,类似的情况:

async Task TestAsync() 
{
    await Task.Delay(1000);
}
Run Code Online (Sandbox Code Playgroud)

Task TestAsync() 
{
    return Task.Delay(1000);
}
Run Code Online (Sandbox Code Playgroud)

最新更新:除了接受的答案之外,LocalCallContext处理方式也有所不同:即使没有异步,CallContext.LogicalGetData也会被恢复.为什么?

nos*_*tio 72

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Stephen Cleary在他的博客中很好地解释了死锁的本质.


另一个主要区别在于异常传播. 一个例外,内抛出await task方法,获取存储在返回的task.Wait()对象和直到任务被通过观察保持休眠task.Result,task.GetAwaiter().GetResult(),asyncOneTestAsync.即使从方法的同步部分抛出,它也会以这种方式传播AnotherTestAsync.

请考虑以下代码,其中DoTestAsync(OneTestAsync, -2)DoTestAsync(AnotherTestAsync, -2)行为完全不同:

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

如果我打电话DoTestAsync,它会产生以下输出:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

注意,我不得不按下Enter才能看到它.

现在,如果我打电话Task.Delay(-2),里面的代码工作流程Task.Delay(1000)是完全不同的,输出也是如此.这一次,我没有被要求按Enter:

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

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

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

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

我在这里这里发布了一些关于TPL异常处理行为的更多细节.


:是否有可能模仿RunSynchronouslyasync基于异步的方法的方法的异常传播行为,以便后者不会抛出相同的堆栈帧?

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

Task TestAsync() 
{
    return Task.Delay(1000);
}
Run Code Online (Sandbox Code Playgroud)

但请注意,在某些条件下(例如,当它在堆栈上太深时),await仍然可以异步执行.

  • 我相信可以通过将 .ConfigureAwait(false) 添加到 await 行来避免第一个示例中的死锁,因为它只会发生,因为该方法试图返回到相同的执行上下文。因此,例外是唯一剩下的区别。 (3认同)
  • @relatively_random,你的评论是正确的,尽管答案是关于“return Task.Run()”和“await Task.Run();”之间的区别。return`,而不是`await Task.Run().ConfigureAwait(false); 返回` (2认同)

Eri*_*ert 53

有什么区别

async Task TestAsync() 
{
    await Task.Delay(1000);
}
Run Code Online (Sandbox Code Playgroud)

Task TestAsync() 
{
    return Task.Delay(1000);
}
Run Code Online (Sandbox Code Playgroud)

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

Func<int> MakeFunction()
{
    Func<int> f = ()=>1;
    return ()=>f();
}
Run Code Online (Sandbox Code Playgroud)

Func<int> MakeFunction()
{
    return ()=>1;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 当然!你睁开眼睛:)在第一种情况下,我创建了一个包装器任务,语义上接近于`Task.Delay(1000).ContinueWith(()= {})`.在第二个中,它只是`Task.Delay(1000)`.差异有点微妙,但很重要. (19认同)
  • 这是一个古老的答案,但我相信今天给出的答案会被否决。它没有回答问题,也没有向OP指出他可以学习的来源。 (10认同)
  • 你能解释一下区别吗?其实我不..谢谢 (4认同)
  • @DanielDubovski:然而,原始海报评论道“当然!你让我大开眼界。差异有些微妙,但很重要”。听起来OP从答案中得到了重要的见解,这就是我给出它的原因。如果您认为这个答案没有帮助,您当然可以自由地否决这个答案,这显然对OP有帮助,但更好的是,如果您留下自己的答案来模拟您认为理想的答案。这样我们就能从你的智慧中学习。 (4认同)
  • 鉴于同步上下文有一个细微的差异,并且异常传播我想说async / await和函数包装之间的差异是不一样的。 (2认同)
  • 当我看到你的答案时,实际上这是一个回答问题的问题,我会得出结论,两者的作用相同,但第一个“MakeFunction”使事情变得复杂:-) (2认同)

Mar*_*zek 11

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

    由于' Program.TestAsync()'是一个返回' Task' 的异步方法,因此返回关键字后面不能跟一个对象表达式.你有意回来' Task<T>'吗?

    它一定要是

    async Task TestAsync()
    {
        await Task.Run(() => DoSomeWork());
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 这两者之间存在重大的概念差异.第一个是异步的,第二个不是.阅读异步性能:了解异步和等待的成本,以获得更多关于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
    
    Run Code Online (Sandbox Code Playgroud)

    .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
    
    Run Code Online (Sandbox Code Playgroud)

  • @ Selman22这就是问题所在:*生成的代码是否有所不同?* (3认同)

Luk*_*oid 9

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

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

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

看到这个问题这个答案非常类似于你的问题和答案.