异步任务<HttpResponseMessage> 获取 VS HttpResponseMessage 获取

Posted

技术标签:

【中文标题】异步任务<HttpResponseMessage> 获取 VS HttpResponseMessage 获取【英文标题】:async Task<HttpResponseMessage> Get VS HttpResponseMessage Get 【发布时间】:2014-11-12 04:20:10 【问题描述】:

我需要您在以下方面的帮助。近一个月来,我一直在阅读有关 Tasks 和 async 的内容。

我想尝试在一个简单的 wep api 项目中实现我新获得的知识。我有以下方法,它们都按预期工作:

 public HttpResponseMessage Get()
 
        var data = _userServices.GetUsers();
        return Request.CreateResponse(HttpStatusCode.OK, data);
 

public async Task<HttpResponseMessage> Get()

        var data = _userServices.GetUsers();


        return await Task<HttpResponseMessage>.Factory.StartNew(() =>
        
           return Request.CreateResponse(HttpStatusCode.OK, data);
        );
 

所以问题。我曾尝试使用提琴手,看看这两者之间有什么区别。异步的更快一点,但除此之外,在 web api 中实现类似的东西的真正好处是什么?

【问题讨论】:

嗯,首先,谢谢大家的回复。你给了我一些“家庭作业”要做。我知道 Factory.StartNew 很糟糕,因为它实际上会在每个请求上启动一个新线程!我也明白“异步操作”必须转发到存储库,这意味着一直到 GetUsers(服务)-> GetUsersAsync(存储库)。因此,我们将拥有一系列异步方法,一直到存储库。存储库必须实现一个支持异步操作的模型,即异步 Ado.Net 或 EntityFramework。 如果我要构建这个 Web Api,我将从 3 层开始:1) 演示 2) 服务和 3) 存储库。存储库将实现 Async Ado.Net 或 Entityframework 。此外,所有这些都将发生在主线程上,但使用 ASYNCHRONOUS 操作。我试图“吞噬”以下书籍 C# 5.0 Cookbook 中的多线程中的理论。似乎所有这些知识都在我的脑海中搞砸了,如果你发现我重复的话,请道歉。我将发布解释我理解的“我认为”的代码。如果你们能再次如此积极地做出贡献,我将不胜感激! 【参考方案1】:

调用发生在主要 IO 操作的位置更有意义。 是的,Async 更快,因为它在执行操作时释放了请求线程。因此,从 Web 服务器的角度来看,您将线程返回给池,服务器可以使用该线程来处理任何将来的调用。

所以例如当您在 SQL Server 上执行搜索操作时,您可能希望执行异步操作并查看性能优势。

它有利于涉及多台服务器的可扩展性。

所以,例如当 SearchRecordAsync 将其 SQL 发送到数据库时,它会返回一个未完成的任务,当请求命中 await 时,它会将请求线程返回到线程池。稍后,当 DB 操作完成时,会从线程池中取出一个请求线程,用于继续请求。

即使您不使用 SQL 操作,假设您想向 10 个人发送电子邮件。在这种情况下,异步也更有意义。

异步也很方便显示长事件的进度。因此,当任务在后台运行时,用户仍将获得活动的 GUI。

要了解,请查看此示例。

在这里,我正在尝试启动名为发送邮件的任务。临时我想更新数据库,而后台正在执行发送邮件任务。

一旦数据库更新发生,它正在等待发送邮件任务完成。但是,通过这种方法,很明显我可以在后台运行任务,并且仍然可以继续使用原始(主)线程。

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program

    public static void Main()
    
        Console.WriteLine("Starting Send Mail Async Task");
        Task task = new Task(SendMessage);
        task.Start();
        Console.WriteLine("Update Database");
        UpdateDatabase();

        while (true)
        
            // dummy wait for background send mail.
            if (task.Status == TaskStatus.RanToCompletion)
            
                break;
            
        

    

    public static async void SendMessage()
    
        // Calls to TaskOfTResult_MethodAsync
        Task<bool> returnedTaskTResult = MailSenderAsync();
        bool result = await returnedTaskTResult;

        if (result)
        
            UpdateDatabase();
        

        Console.WriteLine("Mail Sent!");
    

    private static void UpdateDatabase()
    
        for (var i = 1; i < 1000; i++) ;
        Console.WriteLine("Database Updated!");
    

    private static async Task<bool> MailSenderAsync()
    
        Console.WriteLine("Send Mail Start.");
        for (var i = 1; i < 1000000000; i++) ;
        return true;
    

【讨论】:

非常感谢您的回复。假设我们想要构建一个 web api,它需要每分钟为大容量电子商务提供尽可能多的请求。我们没有 IO 操作,但假设我们有一些昂贵的 SQL 操作。如果我要将我的主要端点装饰为异步,这是一个好建议吗? 嘿伙计,我已经用简单的示例尝试了异步的含义,它与线程相同,但它不那么令人头疼,因为编译器为我们做了一切。【参考方案2】:

在您的服务器上使用async 可以显着提高可伸缩性,因为它可以在async 操作正在进行时释放为请求提供服务的线程来处理其他请求。例如,在同步 IO 操作中,线程将被挂起并且什么也不做,直到操作完成,并且无法为另一个请求提供服务。

话虽如此,使用Task.Factory.StartNew 启动另一个线程,因此您根本无法获得可扩展性优势。您的原始线程可以重复使用,但您已将工作卸载到另一个线程,因此根本没有净收益。事实上,切换到另一个线程是有成本的,但这是最低限度的。

真正的异步操作不会启动线程,我会看看是否存在这样的操作,或者是否可以为Request.CreateResponse 编写一个操作。那么您的代码将更具可扩展性。如果没有,最好还是坚持同步方法。

【讨论】:

【参考方案3】:

正如其他人所指出的,async 在 ASP.NET 上的意义在于它释放了一个 ASP.NET 线程池线程。这对于自然异步操作(例如 I/O 绑定操作)非常有用,因为服务器上的线程少了一个(没有“处理”异步操作的线程,as I explain on my blog)。因此,async 在服务器端的主要好处是可扩展性

但是,您希望避免在 ASP.NET 上使用 Task.Run(更糟糕的是,Task.Factory.StartNew)。我称之为“假异步”,因为它们只是在线程池线程上进行同步/阻塞工作。它们在您希望将工作从 UI 线程中推开以便 UI 保持响应的 UI 应用程序中很有用,但它们(几乎)不应该在 ASP.NET 或其他服务器应用程序上使用。

在 ASP.NET 上使用 Task.RunTask.Factory.StartNew 实际上会降低您的可伸缩性。它们会导致一些不必要的线程切换。对于运行时间较长的操作,您最终可能会放弃 ASP.NET 线程池启发式方法,从而导致创建额外的线程并在随后不必要地销毁。我逐步探索这些性能问题in another blog post。

因此,您需要考虑每个操作在做什么,以及其中的任何操作是否应该是异步的。如果应该,那么该操作应该是异步的。在你的情况下:

public HttpResponseMessage Get()

  var data = _userServices.GetUsers();
  return Request.CreateResponse(HttpStatusCode.OK, data);

Request.CreateResponse 到底在做什么?它只是创建响应对象。就是这样 - 只是一个花哨的new。那里没有 I/O,当然也不需要将其推到后台线程。

不过,GetUsers 更有趣。这听起来更像是数据读取, 基于 I/O。如果您的后端可以扩展(例如,Azure SQL / Tables / 等),那么您应该首先考虑制作async,一旦您的服务公开GetUsersAsync,那么此操作也可能变为async

public async Task<HttpResponseMessage> Get()

  var data = await _userServices.GetUsersAsync();
  return Request.CreateResponse(HttpStatusCode.OK, data);

【讨论】:

以上是关于异步任务<HttpResponseMessage> 获取 VS HttpResponseMessage 获取的主要内容,如果未能解决你的问题,请参考以下文章

线程可以做啥,而基于任务的异步模式(TAP)和任务并行(TPL)与任务(或任务<T>)不能做啥?

如何在服务中调用异步任务<ActionResult> 方法?

C#- 在任务异步调用上返回 IEnumerable<object>

异步任务未加载数据

异步任务<HttpResponseMessage> 获取 VS HttpResponseMessage 获取

覆盖异步任务时应用程序崩溃