【问题标题】:Is having a return type of Task enough to make a method run asynchronously?是否具有 Task 的返回类型足以使方法异步运行?
【发布时间】:2020-05-26 22:13:33
【问题描述】:

我有一个简单的方法,它执行复杂的字符串操作并返回结果。可以看到,这个方法的返回类型是Task<string>。因此,我可以使用Task.FromResult(result) 来返回我的字符串的值。

public Task<string> ComplexOperation()
{
    string result = // Do something complex
    return Task.FromResult(result);
}

然后,我可以使用await 关键字来调用这个方法。

public static async Task Main(string[] args)
{
    var myResult = await ComplexOperation();
}

因为我在等待CompelxOperation(),(等待任务完成返回)这个方法会异步运行吗?

【问题讨论】:

  • ..你试过了吗? ;)
  • @FranzGleichmann 是的,它有效。只是不确定它是否实际上是异步运行的。
  • @MatthewTrip 不会)因为Task.FromResult 不是异步操作)
  • @MatthewTrip 错过了// Do something complex 评论。是的,您将需要TaskCompletionSource 并将您的工作转移到另一个线程,因此它不会阻塞调用线程。
  • @MatthewTrip 或 Task.Run(..) 将完成这项工作。

标签: c# asynchronous async-await task-parallel-library


【解决方案1】:

根据您的 cmets,您需要执行以下操作:

public Task<string> ComplexOperation()
{
    return Task.Run(() =>  /* Do something complex */);
}

你可以这样玩:

public static async Task Main()
{
    Console.WriteLine("Before" );
    var myResultTask = ComplexOperation();
    Console.WriteLine("After task creation");
    var result = await myResultTask;
    Console.WriteLine("After async await");
}

public Task<string> ComplexOperation()
{    
    Console.WriteLine("Creation");
    return Task.Run(() =>
    {
        Console.WriteLine("In before work");
        Thread.Sleep(500); //simulate work;
        Console.WriteLine("In after work");
        return "Done";
    });
}

当您返回 Task.FromResult 时,将行为与切换到您的实现进行比较。在这样的测试示例中也没有多大意义,TBH。

【讨论】:

  • @Servy 在我看来,使用Task.Run 意味着使用Task.FromResult(result) 可以避免创建状态机的不必要开销。如果我们使用 asyncawaitTask.FromResult(result); 的组合,那么代码将是异步
  • @StepUp Task.Run 不会创建状态机。 async 创建状态机。您实际上是在谈论将非状态机解决方案转变为的解决方案,与您所说的相反。此外,asyncawait 是用于操作异步操作的工具,Task.Run 是用于创建异步操作的工具(通过在线程池线程中异步运行同步方法)。您的回答提供了一些甚至不是异步的。它只是一种同步方法,增加了一些状态机开销,没有提供任何价值。
  • @Servy 好的,我明白了!但是,为什么this article 说该方法是异步的?
  • @StepUp 不。这是说该方法返回一个Task&lt;int&gt;。这实际上只是关于方法的返回类型的事实,仅此而已。它并没有说该方法是异步的。另请注意,评论明确指出FromResult 用作实际上异步方法的占位符,并且该方法的讨论基于此,而不是占位符(实际上并不是说现有方法实际上也是异步的)。
  • @StepUp 这个方法返回一个已完成的任务。返回完成的任务的方法是不同步的。无论调用者是否立即等待它都是如此。
【解决方案2】:

正如您将 Main 方法标记为异步以获取异步方法一样,您需要使用 async 关键字将该方法标记为异步,并将结果设为 Task。

但在框架中,任务用于 2 个相关但不同的概念,并行化和异步编程。

你正在做的是使用并行化。

所以你只是在不同的线程上运行一些同步代码。

根据您的示例,我认为您需要的是使用并行化,这可以加快一些复杂的计算或通过使用多个线程并行工作。

异步代码背后的概念是在您等待外部资源(例如来自 Web 服务的某些数据)时释放线程,以便在此期间完成其他工作。

因此,如果您需要使用本地资源进行复杂的工作,最好使用没有异步逻辑的任务,而不是在使用远程资源时进行异步操作。

【讨论】:

    【解决方案3】:

    从语义上讲,ComplexOperation 方法是异步的,因为它返回一个可等待的类型,为了遵循 the guidelines,它应该命名为 ComplexOperationAsync

    TAP 中的异步方法在返回可等待类型的方法的操作名称后包含Async 后缀,例如TaskTask&lt;TResult&gt;ValueTaskValueTask&lt;TResult&gt;

    但这不是一个表现良好的异步方法。异步方法预计会立即返回不完整的Task,从而允许调用者异步执行await 任务而不会被阻塞。来自docs

    基于 TAP 的异步方法可以在返回结果任务之前同步执行少量工作,例如验证参数和启动异步操作。应将同步工作保持在最低限度,以便异步方法可以快速返回。

    ComplexOperation 方法正好相反:它强制调用线程执行复杂的操作,最后它交还一个完成的任务。出于所有意图和目的,此操作根本不是异步的。它是 100% 同步和 0% 异步,加上一些开销(雪上加霜)。所以不要这样做,如果有一些库可以这样做,请不要使用该库。这简直是​​不好的做法。


    澄清:由于术语异步方法在不同的上下文中可能意味着不同的东西,我应该澄清这个答案的上下文是消费者的角度,谁看到方法的签名作为契约,并基于可见契约而不是不可见的实现来构建期望。在这种情况下,方法ComplexOperation 是异步的。如果我们切换视角并专注于实现,ComplexOperation 方法就不是异步的。我的说法«it is simply bad practice»指的是打破契约的做法,并为具有异步契约的方法提供同步实现。我并不是在批评 Task.FromResult 方法本身的使用。我只在它遵循复杂/冗长/潜在操作时才批评它,根据方法的约定应该使其异步。

    附:我感谢@DmytroMukalov 提供(在chat 中)通过合同和实现异步之间的有益区别。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多