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

2023-11-23

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

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

and

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

生成的代码也不同吗?

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

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

and

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

最新更新:除了接受的答案之外,在方式上也存在差异LocalCallContext得到处理:即使没有异步,CallContext.LogicalGetData 也会恢复。为什么?


一个主要区别在于异常传播。异常,抛出在async Task方法,存储在返回的Task对象并保持休眠状态,直到通过观察任务await task, task.Wait(), task.Result or task.GetAwaiter().GetResult()。即使从同步的一部分async method.

考虑以下代码,其中OneTestAsync and AnotherTestAsync行为完全不同:

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),它会产生以下输出:



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
  

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

附带说明一下,错误传播逻辑仍然不同async void methods(相对于async Task方法)。内部引发的异常async void方法将立即在当前线程的同步上下文中重新抛出(通过SynchronizationContext.Post),如果当前线程有一个 (SynchronizationContext.Current != null)。否则,它将被重新抛出ThreadPool.QueueUserWorkItem)。调用者没有机会在同一堆栈帧上处理此异常。

我发布了一些有关 TPL 异常处理行为的更多详细信息here and here.


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

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

// 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仍然可以异步执行。


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
}

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

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

斯蒂芬·克利里 (Stephen Cleary) 在他的著作中很好地解释了死锁的本质。blog.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

“await Task.Run(); return;”之间的任何区别和“返回Task.Run()”? [复制] 的相关文章

随机推荐