【问题标题】:Benefits from returning a "Task<int>" instead of an "int" in a WCF or WebAPI method (IIS)在 WCF 或 WebAPI 方法 (IIS) 中返回“Task<int>”而不是“int”的好处
【发布时间】:2023-04-03 12:55:01
【问题描述】:

考虑一个耗时的同步方法“Foo”:

public int Foo(int id)
{
    // Do some expensive calculation
    return 42;
}

还有一个托管在 IIS 中的称为“FooService”的 WCF 服务,它调用“Foo”:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class FooService 
{
    public Task<int> GetFoo(int id)
    {
        return Task.Factory.StartNew(() => return Foo(id));
    }

    public int GetFoo2(int id)
    {
        return Foo(id);
    }
}

如果我为 Foo 启动并返回任务,我会获得任何好处吗?像 - 我是否减少了 I/O 线程的负载?

【问题讨论】:

  • 您不应该使用Task.Factory.StartNewTask.RunASP.NET 中包装一个同步的、受CPU 限制的工作方法。所有这些只是增加了线程切换的开销。调用Task.Factory.StartNew 并将其作为Task 返回的HTTP 请求至少需要相同数量的线程才能完成,您不会提高可伸缩性,只会降低性能。 OTOH,这对于自然 IO 绑定的任务确实有意义,这些任务在挂起时不使用线程。

标签: c# wcf iis task-parallel-library task


【解决方案1】:

WCF 中的任务提供了比 APM 模式(BeginXX/EndXX)更方便的 API。为什么要在 WCF 中使用异步调用?因为,如果做得正确,它会带来更好的线程利用率,进而让您的应用程序更具可扩展性。

在客户端,拥有基于任务的服务契约,可以更轻松地调用服务,而不会阻塞线程,等待调用返回。即使该操作是服务器端的同步、受 CPU 限制的操作,客户端也会从任务中受益。此外,对于任务,通过调用Task.Wait()Task&lt;T&gt;.Result 再次使调用同步非常简单。

在服务端,基于任务的操作在以下几种情况下很有用:

  • 您希望并行化一些 CPU 密集型操作
  • 您正在执行 IO(例如调用另一个服务、读取文件等)
  • 以上任意组合

每次调用 WCF 操作时,WCF 都会从线程池中抓取一个线程来处理请求。所以不需要调用StartNew,它将操作排队到线程池(完全冗余的开销):

public Task<int> GetFoo(int id)
{
    return Task.Factory.StartNew(() => return Foo(id));
}

相反,您可以使用FromResult,它会创建一个已完成的任务对象:

public Task<int> GetFoo(int id)
{
    return Task.FromResult(new Foo(id));
}

最后,如果上述用例都不相关,并且您的客户端 API 需要同步,那么使用任务就没有意义了。

【讨论】:

  • 谢谢伊莱。大多数方法都会执行一些耗时的数据库查询,所以我认为返回一个任务是个好主意。我认为使用 Task.Factory.StartNew 将允许我运行更多并发请求 - 而 Task.FromResult 不会?
  • FromResult 只是创建一个包含结果对象的任务对象——它只是一个不运行任何东西的包装器。如果你有一些你希望并行化的操作,你有很多选择(阅读:async/await,Task.WhenAllParallel.ForEach,在 Entity Framework 6 中使用异步。)我建议你做更多的研究,因为这是一个相当大的主题。
【解决方案2】:

请定义一个耗时的同步方法

异步操作的开销不可忽略。只有在执行 IO-bound 操作(调用另一个 Web 服务/API、读取文件、从数据库读取大量数据或运行慢查询)时,才应该使用任务。

在服务器端使用异步编程不同于在客户端。我们关心的不是 UI 线程,而是可扩展性。在服务器端使用异步编程将允许您运行数千个并发请求。

从 IIS 7 开始,这一点非常重要,正如 here 所解释的那样

... 在 IIS 7.0 集成模式下,ASP.NET 限制了 并发执行请求。差异仅在 请求是异步的(请求要么具有异步 处理程序或管道中的模块异步完成)。 显然,如果请求是同步的,那么 并发执行请求与线程数相同 并发执行请求,但如果请求是异步的 那么这两个数字可能会有很大的不同 请求多于线程。

注意:从 .net 4.5 开始,最好使用 Task.Run。

【讨论】:

  • 非常感谢您的回答。大多数方法都会执行一些数据库查询,这可能需要一些时间(1 - 5 秒)。所以如果我在这里返回一个任务,我的服务可以处理更多的并发请求吗?请注意缺少 async 关键字。
  • 也感谢 Task.Run 提示
  • @jisaak 是的,特别是如果你一直遵循异步模式
【解决方案3】:

在我看来,不,您不会从分拆任务线程来完成帖子中描述的工作中获得任何好处。原因是这样的:使用 WSHttpBinding 或 BasicHTTPBinding 定义的每个 WCF Web 服务(不能与其他人交谈)将为每个传入请求创建自己的独立线程,所以我看不到优势。因此,如果 12:00:01:00 有 10 个请求,每个请求都会自动从 WCF 获取它自己的线程。

现在,如果您希望在每个单一请求中使用线程来加速一些重复、耗时的任务,那么您显然会从通过 Task 设置和管理线程中受益,但仅此而已。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-01
    • 2023-03-30
    相关资源
    最近更新 更多