从同步函数返回 Task 与 void

Hei*_*ich -2 c# task-parallel-library


我只是对从同步函数返回“Task”与“void”的场景感到好奇。假设我有一个函数,如下所示,它可以返回任务或空值。我只是好奇返回“Task”而不仅仅是“void”是否有任何好处?


public void SynchronousFunction(/* some input */) { /* some synchronous operation */}

// VS 

public Task SynchronousFunction(/* some input */)
{
    /* some synchronous operation */
    return Task.CompletedTask
}

// VS Variation of Task return function

public Task SynchronousFunction(/* some input */)
{      
   return Task.Run(() => /* some synchronous operation */
}
Run Code Online (Sandbox Code Playgroud)

我的观点有两点。首先,感觉返回任务需要一些不必要的装箱和拆箱,其次,如果在某些同步代码中调用该函数,则不需要使用 .Wait() 或类似的东西来调用。

Enr*_*one 5

在第一个示例中,您的代码同步执行。调用该方法的调用线程被阻塞,等待方法执行完成,然后再执行其他操作。

在某些情况下,这很好(例如,您需要运行一些脚本程序来维护数据库),但有时这是一个问题(例如,控制器代码中 ASP.NET Core 应用程序内的服务器端代码)。

第二个示例基本相同,但您返回的是已完成的任务。代码仍然同步完成,没有区别。调用者返回一个已经完成的任务对象。对该任务的任何形式的等待(使用Wait()方法的同步等待或使用async关键字的异步等待)都会立即完成,因为Task对象已经完成。不涉及装箱行为,因为Task是引用类型。相反,有分配,因为您正在创建引用类型的实例(请查看 的文档Task.CompletedTask。他们明确表示多次调用它并不能保证始终返回相同的实例,换句话说,它不是缓存的实例) 。

请注意,此方法签名对于方法调用者来说有些混乱:它可能认为此方法正在执行 I/O 工作,但实际上它正在执行 CPU 密集型工作。您应该清楚地记录此行为,或者如果可以的话最好完全避免此签名。

在第三个示例中,您要求线程池执行一些工作,并且返回一个Task表示已卸载到线程池的正在进行的操作的对象。该Task对象基本上是一个代理,您可以使用它来等待分配给线程池的工作负载完成。

当该方法返回时,您只需知道您已要求线程池执行某些操作,该工作何时以及如何完成完全由运行时控制。调用代码通常等待任务完成。这可以通过阻塞线程来同步完成(例如Wait()),也可以在不阻塞线程的情况下异步完成(例如await)