一尘不染

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

c#

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

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


阅读 1589

收藏
2020-05-19

共1个答案

一尘不染

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

考虑以下代码,其中OneTestAsync和的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),它将产生以下输出:

按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)在他的博客中很好地解释了这种僵局。

2020-05-19