返回Task的方法的不同实现方式

3

我在重构一些遗留代码时遇到了这个问题。

考虑一个返回 Task 的接口方法:

public interface IFoo
{
    Task Bar();
}
< p > Bar 方法的实现可以有两种方式:

返回 Task

public class Foo1 : IFoo
{
    public Task Bar()
    {
        return Task.Run(() =>
            {
                /* some work */
            });
    }
}

或者使用 async ... await:

public class Foo2 : IFoo
{
    public async Task Bar()
    {
        await Task.Run(() =>
            {
                /* some work */
            });
    }
}

这些实现功能上是否等价,或者存在(潜在的微妙)差异?

1
据我所知,差异并不像你在这里所呈现的那样。当你实现Foo1时,你需要调用Task.Wait(),而对于Foo2,只需等待Task,因此存在差异。在这里解释:https://dev59.com/vGkw5IYBdhLWcg3w_Pbn - Razvan Dumitru
@RazvanDumitru 为什么要调用Task.Wait()呢?对于调用者而言,这两种方法是相同的。await foo1Instance.Bar();await foo2Instance.Bar();将以相同的方式工作。没有理由阻塞第一个方法只是为了返回一个已完成的任务。也许你想表达其他意思吗? - Panagiotis Kanavos
是的,你说得对。根本不需要等待。你可以同时等待返回的两个对象。 - Razvan Dumitru
3个回答

2

使用async-await语法后,编译器生成的代码在任务完成后实际上会继续执行await语句之后的代码。此外,等待一个已经失败的任务会导致异常向awaiter冒泡,这意味着它不再被视为未观察到的。我认为还有更多的内容,我真的建议你看一下Jon Skeet在pluralsight的C# 5 Async课程,他会解释编译器生成的代码。


1

返回一个任务

Foo1.Bar 是一个普通的同步方法,它返回一些对象实例(特别是 Task)。

或者使用 async ... await

Foo2.Bar 是一个异步方法,将被编译成状态机。这里会有一些额外的开销。 请注意,Roslyn 的未来版本 将把 这样的方法转换为同步方法。

但实际上,在这里不应该使用 Task.Run。有关详细信息,请参见这篇 文章


这并不是文章所说的。最终它确实使用了Task.Run。建议不要在方法的实现中使用Task.Run来自动将CPU绑定方法转换为“异步”方法。 - Panagiotis Kanavos
另外,两种 方法都是异步的。它们都使用单独的任务执行其工作。然而,第二种方法实际上等待某些其他异步调用。 - Panagiotis Kanavos
@PanagiotisKanavos:我不同意。第一个方法同步的。它只是启动了一些任务,但方法本身没有任何异步操作——从方法中启动任务或线程并不会使其异步化。此外,您误解了链接的帖子。当实现不是“真正”的异步代码时,它是关于允许调用者决定是否使用Task.Run的。 - Dennis

0

嗯,第二个实现看起来过于复杂了。如果你只需要返回一个任务,就像第一个实现那样创建一个任务并返回它。

第二个实现返回一个封装了另一个等待任务的任务。这将导致不必要的代码膨胀、内存使用和可能会创建另一个线程,只为达到与第一个相同的目标。


第二个代码片段允许在任务完成后执行更多的代码,前提是没有抛出异常。无论如何,目标并未被讨论 - 问题围绕两者之间的区别。 - Eyal Perry
@EyalPerry那是不正确的。您可以使用ContinueWith在任务完成后运行代码。这就是.NET 4.0中使用任务的方式,当由async/await生成的状态机不可取时仍然使用。 - Panagiotis Kanavos
你说的没错,但并不否认我所说的。@PanagiotisKanavos - Eyal Perry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接