“await Task.Run();"之间的任何区别;返回;"和“返回 Task.Run()"? [英] Any difference between "await Task.Run(); return;" and "return Task.Run()"?

查看:53
本文介绍了“await Task.Run();"之间的任何区别;返回;"和“返回 Task.Run()"?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下两段代码在概念上有区别吗:

异步任务 TestAsync(){await Task.Run(() => DoSomeWork());}

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

生成的代码也不同吗?

为了避免与 Task.Run 混淆,类似的情况:

异步任务 TestAsync(){等待 Task.Delay(1000);}

Task TestAsync(){返回 Task.Delay(1000);}

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

解决方案

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

考虑以下代码,其中 OneTestAsyncAnotherTestAsync 的行为完全不同:

静态异步任务 OneTestAsync(int n){等待 Task.Delay(n);}静态任务 AnotherTestAsync(int n){返回 Task.Delay(n);}//使用 OneTestAsync 或 AnotherTestAsync 作为 whatTest 调用 DoTestAsyncstatic void DoTestAsync(Func whatTest, int n){任务 task = null;尝试{//开始任务任务 = whatTest(n);//做一些其他的事情,//当任务挂起时Console.Write("按回车继续");Console.ReadLine();任务.等待();}捕获(异常前){Console.Write("错误:" + ex.Message);}}

如果我调用 DoTestAsync(OneTestAsync, -2),它会产生以下输出:

<前>按回车继续错误:发生了一个或多个错误.await Task.Delay错误:第二

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

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

<前>错误:该值必须是 -1(表示无限超时)、0 或正整数.参数名称:millisecondsDelayError:第一个

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

顺便提一下,async void 方法(与async Task 方法相对)的错误传播逻辑是不同的.async void 方法中引发的异常将立即在当前线程的同步上下文中重新抛出(通过 SynchronizationContext.Post),如果当前线程有一个(<代码>SynchronizationContext.Current != null).否则,它将通过 ThreadPool.QueueUserWorkItem 重新抛出).调用者没有机会在同一个堆栈帧上处理这个异常.

我在此处此处.

<小时>

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

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

//异步异步任务MethodAsync(int arg){如果(参数<0)throw new ArgumentException("arg");//...返回 42 + 参数;}//非异步任务MethodAsync(int arg){var task = new Task(() =>{如果(参数<0)throw new ArgumentException("arg");//...返回 42 + 参数;});task.RunSynchronously(TaskScheduler.Default);返回任务;}

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

<小时>另一个显着的区别是async/await 版本更容易在非默认同步上下文中死锁.例如,以下内容将在 WinForms 或 WPF 应用程序中死锁:

静态异步任务 TestAsync(){等待 Task.Delay(1000);}void Form_Load(对象发送者,EventArgs e){TestAsync().Wait();//这里死锁}

把它改成非异步版本就不会死锁了:

Task TestAsync(){返回 Task.Delay(1000);}

Stephen Cleary 在他的 博客.

Is there any conceptual difference between the following two pieces of code:

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

and

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

Does the generated code differ, either?

EDIT: To avoid confusion with Task.Run, a similar case:

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

and

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

LATE UPDATE: In addition to the accepted answer, there is also a difference in how LocalCallContext gets handled: CallContext.LogicalGetData gets restored even where there is no asynchrony. Why?

解决方案

One major difference is in exception propagation. An exception, thrown inside an async Task method, gets stored in the returned Task object and remains dormant until the task gets observed via await task, task.Wait(), task.Result or task.GetAwaiter().GetResult(). It is propagated this way even if thrown from the synchronous part of the async method.

Consider the following code, where OneTestAsync and AnotherTestAsync behave quite differently:

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

If I call DoTestAsync(OneTestAsync, -2), it produces the following output:

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

Note, I had to press Enter to see it.

Now, if I call DoTestAsync(AnotherTestAsync, -2), the code workflow inside DoTestAsync is quite different, and so is the output. This time, I wasn't asked to press Enter:

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

In both cases Task.Delay(-2) throws at the beginning, while validating its parameters. This might be a made-up scenario, but in theory Task.Delay(1000) may throw too, e.g., when the underlying system timer API fails.

On a side note, the error propagation logic is yet different for async void methods (as opposed to async Task methods). An exception raised inside an async void method will be immediately re-thrown on the the current thread's synchronization context (via SynchronizationContext.Post), if the current thread has one (SynchronizationContext.Current != null). Otherwise, it will be re-thrown via ThreadPool.QueueUserWorkItem). The caller doesn't have a chance to handle this exception on the same stack frame.

I posted some more details about TPL exception handling behaviour here and here.


Q: Is it possible to mimic the exception propagation behavior of async methods for non-async Task-based methods, so that the latter doesn't throw on the same stack frame?

A: If really needed, then yes, there is a trick for that:

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

Note however, under certain conditions (like when it's too deep on the stack), RunSynchronously could still execute asynchronously.


Another notable difference is that the async/await version is more prone to dead-locking on a non-default synchronization context. E.g., the following will dead-lock in a WinForms or WPF application:

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

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

Change it to a non-async version and it won't dead-lock:

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

The nature of the dead-lock is well explained by Stephen Cleary in his blog.

这篇关于“await Task.Run();"之间的任何区别;返回;"和“返回 Task.Run()"?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆