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


90

以下两段代码之间在概念上有什么区别:

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。为什么?


1
是的,这有所不同。而且差异很大。否则根本就没有使用await/async的意义:)
MarcinJuraszek 2014年

1
我认为这里有两个问题。1.方法的实际实现对调用者有影响吗?2.两种方法的编译表示形式是否不同?
DavidRR

Answers:


80

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

考虑下面的代码,其中OneTestAsyncAnotherTestAsync表现完全不同:

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);
    }
}

如果我打电话 DoTestAsync(OneTestAsync, -2),它将产生以下输出:

按Enter继续
错误:发生一个或多个错误。等待Task.Delay
错误:第二

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

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

错误:该值必须为-1(表示无限超时),0或正整数。
参数名称:millisecondsDelayError:1st

在这两种情况下Task.Delay(-2),在验证其参数时都在开始时抛出。这可能是一个虚构的场景,但理论上Task.Delay(1000)也可能会抛出异常,例如,当基础系统计时器API发生故障时。

在一个侧面说明,误差传播逻辑为尚未不同async void的方法(而不是async Task方法)。如果当前线程有一个(。,它将async void通过SynchronizationContext.Post)重新抛出异常,方法内部引发的异常将立即通过()通过当前线程的同步上下文SynchronizationContext.Current != null)重新抛出ThreadPool.QueueUserWorkItem。调用者没有机会在同一堆栈帧上处理此异常。

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


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

:如果确实需要,那么可以,有一个窍门:

// 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;
}

但是请注意,在某些情况下(例如当堆栈太深时),RunSynchronously仍可以异步执行。


另一个显着区别是,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)在其博客中很好地解释了这种僵局。


2
我相信可以通过在.wait行中添加.ConfigureAwait(false)来避免第一个示例中的死锁,因为这只是因为该方法试图返回相同的执行上下文而发生。因此,exeption是剩下的唯一区别。
relative_random

2
@relatively_random,您的评论是正确的,但得到的答案是约之间的区别return Task.Run()await Task.Run(); return,而不是await Task.Run().ConfigureAwait(false); return
noseratio

如果您在按Enter键后发现程序关闭,请确保执行ctrl + F5而不是F5。
David Klempfner '19

54

之间有什么区别

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;
}

无论我的两件事之间有什么区别,相同的区别就是你的两件事之间。


22
当然!您睁开了眼睛:)在第一种情况下,我创建了一个包装器任务,从语义上讲接近于Task.Delay(1000).ContinueWith(() = {})。在第二个中,它只是Task.Delay(1000)。差异有些微妙,但意义重大。
2014年

3
你能解释一下区别吗?其实我不知道。谢谢您
yu

4
鉴于同步上下文有细微的差别,并且异常传播我想说async / await和函数包装之间的区别是不一样的。
卡梅隆·麦克法兰

1
@CameronMacFarland:这就是为什么我要求澄清。问题是两者之间在概念上有区别。好吧,我不知道。当然有很多区别;他们中的任何一个都算作“概念上的”差异吗?在我的嵌套函数示例中,错误传播也有所不同。如果在局部状态下关闭功能,则局部生存期会有差异,依此类推。这些是“概念上的”差异吗?
埃里克·利珀特

6
这是一个旧的答案,但是我相信今天给出的答案会被否决。它没有回答问题,也没有将OP指向他可以学习的来源。
丹尼尔·杜波夫斯基

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

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

    它一定要是

    async Task TestAsync()
    {
        await Task.Run(() => DoSomeWork());
    }
    
  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
    

    .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,确实没有编译。那是一个错字,我相信你做对了。否则,一个很好的答案,谢谢!我认为C#可能足够聪明,可以避免在第一种情况下生成状态机类。
2014年

9

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

相反,如果标记方法asyncawait则将失去等待的能力。(也就是说,在方法本身内;方法的调用者仍可以等待该方法。)但是,通过避免使用async关键字,您不再需要生成状态机,而状态机会增加相当多的开销(将局部变量提升为字段)状态机,GC的其他对象)。

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

看到这个问题这个答案与您的问题和这个答案非常相似。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.