"await Task.Run(); return;" 和 "return Task.Run()" 之间有什么区别吗?

26 浏览
0 Comments

"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会恢复?

0
0 Comments

任何两个在"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/'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/'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/'d__1'::'<>t__builder'

IL_0014: ldloca.s 0

IL_0016: ldc.i4.m1

IL_0017: stfld int32 SOTestProject.Program/'d__1'::'<>1__state'

IL_001c: ldloca.s 0

IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'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::Startd__1'>(!!0&)

IL_002d: ldloca.s 0

IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'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::'b__4'()

IL_0008: newobj instance void class [mscorlib]System.Func`1::.ctor(object, native int)

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#可能足够聪明,能够避免在第一种情况下生成状态机类。

0
0 Comments

问题的出现原因是作者对两种代码的区别感到困惑,希望得到解释。解决方法是通过提出另一个问题来澄清,以便更好地理解两者之间的区别。

在第一个问题中,作者提到了两段代码,一段是使用async/await关键字的异步方法,另一段是使用return关键字的普通方法。作者想知道这两者之间有什么区别。

在第二个问题中,作者使用了另一个示例来解释第一个问题。这个示例是关于函数的包装器,作者想知道两种方法之间的概念上的区别。

然后,其他人对这个问题进行了讨论。有人指出,两种方法之间存在细微但重要的区别,特别是在同步上下文和异常传播方面。还某些情况下了在嵌套函数中的差异,并问这些差异是否被认为是“概念上的”差异。

最后,有人指出这个回答并没有直接回答问题,但是原始问题的提问者表示这个回答让他们有了重要的启示,并表示感谢。同时,他还鼓励其他人提供更好的回答。

这个问题的出现原因是作者对两种代码之间的差异感到困惑,解决方法是通过提出问题来澄清。整个讨论过程中,人们对这个问题进行了深入的探讨,并对各自的观点进行了阐述。

0
0 Comments

"await Task.Run(); return;"和"return Task.Run()"之间有什么区别?

一个主要的区别在于异常传播。在async Task方法中抛出的异常会存储在返回的Task对象中,并在通过await tasktask.Wait()task.Resulttask.GetAwaiter().GetResult()观察到任务时传播。即使异常是从同步部分抛出的,也会以这种方式传播。

考虑以下代码,其中OneTestAsyncAnotherTestAsync的行为有很大的区别:

static async Task OneTestAsync(int n)
{
    await Task.Delay(n);
}
static Task AnotherTestAsync(int n)
{
    return Task.Delay(n);
}
static void DoTestAsync(Func whatTest, 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重新抛出。调用者没有机会在同一堆栈帧上处理此异常。

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


Q:是否可以模拟async方法的异常传播行为,使非异步Task方法不会在同一堆栈帧上抛出异常?

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

// async
async Task MethodAsync(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运行。

0