"await Task.Run(); return;" 和 "return Task.Run()" 之间有什么区别吗?
"await Task.Run(); return;" 和 "return Task.Run()" 之间有什么区别吗?
以下两段代码有概念上的区别吗:\n
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }
\n和\n
Task TestAsync() { return Task.Run(() => DoSomeWork()); }
\n生成的代码有区别吗?\n编辑:为了避免与Task.Run
混淆,类似的情况:\n
async Task TestAsync() { await Task.Delay(1000); }
\n和\n
Task TestAsync() { return Task.Delay(1000); }
\n最新更新:除了接受的答案之外,LocalCallContext
的处理也有差异:即使没有异步,为什么CallContext.LogicalGetData会恢复?
任何两个在"await Task.Run(); return;"和"return Task.Run()"之间的区别?
这个问题的出现的原因是因为第一种方法根本无法编译。
原因是因为'Program.TestAsync()'是一个返回'Task'的异步方法,return关键字后面不能跟随一个对象表达式。你是否打算返回'Task
所以正确的写法是:
async Task TestAsync()
{
await Task.Run(() => DoSomeWork());
}
这两种方法之间有一个重大的概念上的区别。第一种是异步的,第二种不是。阅读"Async Performance: Understanding the Costs of Async and Await"可以更多地了解async/await的内部。
它们生成的代码也是不同的。
.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 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/'
[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/'
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/'
IL_0014: ldloca.s 0
IL_0016: ldc.i4.m1
IL_0017: stfld int32 SOTestProject.Program/'
IL_001c: ldloca.s 0
IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'
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
IL_002d: ldloca.s 0
IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'
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::'
IL_0008: newobj instance void class [mscorlib]System.Func`1
IL_000d: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Func`1
IL_0012: stloc.0
IL_0013: br.s IL_0015
IL_0015: ldloc.0
IL_0016: ret
} // end of method Program::TestAsync2
虽然它没有编译,但这是一个拼写错误,我确定你写对了。否则,非常好的答案,谢谢!我认为C#可能足够聪明,能够避免在第一种情况下生成状态机类。
问题的出现原因是作者对两种代码的区别感到困惑,希望得到解释。解决方法是通过提出另一个问题来澄清,以便更好地理解两者之间的区别。
在第一个问题中,作者提到了两段代码,一段是使用async/await关键字的异步方法,另一段是使用return关键字的普通方法。作者想知道这两者之间有什么区别。
在第二个问题中,作者使用了另一个示例来解释第一个问题。这个示例是关于函数的包装器,作者想知道两种方法之间的概念上的区别。
然后,其他人对这个问题进行了讨论。有人指出,两种方法之间存在细微但重要的区别,特别是在同步上下文和异常传播方面。还某些情况下了在嵌套函数中的差异,并问这些差异是否被认为是“概念上的”差异。
最后,有人指出这个回答并没有直接回答问题,但是原始问题的提问者表示这个回答让他们有了重要的启示,并表示感谢。同时,他还鼓励其他人提供更好的回答。
这个问题的出现原因是作者对两种代码之间的差异感到困惑,解决方法是通过提出问题来澄清。整个讨论过程中,人们对这个问题进行了深入的探讨,并对各自的观点进行了阐述。
"await Task.Run(); return;"和"return Task.Run()"之间有什么区别?
一个主要的区别在于异常传播。在async Task
方法中抛出的异常会存储在返回的Task
对象中,并在通过await task
,task.Wait()
,task.Result
或task.GetAwaiter().GetResult()
观察到任务时传播。即使异常是从同步部分抛出的,也会以这种方式传播。
考虑以下代码,其中OneTestAsync
和AnotherTestAsync
的行为有很大的区别:
static async Task OneTestAsync(int n) { await Task.Delay(n); } static Task AnotherTestAsync(int n) { return Task.Delay(n); } static void DoTestAsync(FuncwhatTest, int n) { Task task = null; try { task = whatTest(n); Console.Write("Press enter to continue"); Console.ReadLine(); task.Wait(); } catch (Exception ex) { Console.Write("Error: " + ex.Message); } }
如果我调用DoTestAsync(OneTestAsync, -2)
,它会产生以下输出:
Press enter to continue Error: One or more errors occurred.await Task.Delay Error: 2nd
请注意,我必须按Enter键才能看到它。
现在,如果我调用DoTestAsync(AnotherTestAsync, -2)
,则DoTestAsync
中的代码工作流程会有所不同,输出也会有所不同。这次,我不需要按Enter键:
Error: The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer. Parameter name: millisecondsDelayError: 1st
在两种情况下,Task.Delay(-2)
在开始时抛出异常,验证其参数。这可能是一个虚构的场景,但理论上Task.Delay(1000)
也可能抛出异常,例如当底层系统计时器API失败时。
顺便说一句,对于async void
方法(而不是async Task
方法),错误传播逻辑是不同的。在async void
方法中引发的异常将立即在当前线程的同步上下文中重新抛出(通过SynchronizationContext.Post
),如果当前线程有一个同步上下文(SynchronizationContext.Current != null
)。否则,它将通过ThreadPool.QueueUserWorkItem
重新抛出。调用者没有机会在同一堆栈帧上处理此异常。
Q:是否可以模拟async
方法的异常传播行为,使非异步Task
方法不会在同一堆栈帧上抛出异常?
A:如果确实需要,那么是的,有一个小技巧:
// async async TaskMethodAsync(int arg) { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; } // non-async Task MethodAsync(int arg) { var task = new Task (() => { 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(); // 死锁在这里 }
将其更改为非异步版本,它将不会发生死锁:
Task TestAsync() { return Task.Delay(1000); }
死锁的性质由Stephen Cleary在他的博客中很好地解释了。
我相信第一个示例中的死锁可以通过在await行添加.ConfigureAwait(false)来避免,因为它之所以发生是因为方法试图返回到相同的执行上下文。因此,异常是唯一剩下的不同之处。
如果你发现程序在按下Enter后关闭,请确保使用ctrl+F5而不是F5运行。