【问题标题】:Asynchronous DB-Query to trigger Stored Procedure异步 DB-Query 触发存储过程
【发布时间】:2015-06-14 13:18:15
【问题描述】:

我想在 C# 中执行一个异步数据库查询,该查询调用备份的存储过程。由于我们使用 Azure,这大约需要 2 分钟,我们不希望用户等待那么久。

所以想法是让它异步,以便任务在请求之后继续运行。

[HttpPost]
public ActionResult Create(Snapshot snapshot)
{
    db.Database.CommandTimeout = 7200;
    Task.Run(() => db.Database.ExecuteSqlCommandAsync("EXEC PerformSnapshot @User = '" + CurrentUser.AccountName + "', @Comment = '" + snapshot.Comment + "';"));
    this.ShowUserMessage("Your snapshot has been created.");
    return this.RedirectToActionImpl("Index", "Snapshots", new System.Web.Routing.RouteValueDictionary());
}

恐怕我还没有理解异步任务的概念。如果我不使用等待语句,查询将不会被执行(或中止?)。但实际上“等待”是我特别不想在这里做的一件事。

那么...为什么我不得不在这里使用等待?

或者该方法会被启动,但如果请求完成则被杀死?

【问题讨论】:

    标签: c# asp.net entity-framework tsql asynchronous


    【解决方案1】:

    我们不希望用户等待那么久。

    async-await 帮不了你。听起来很奇怪,基本的async-await 模式是关于以非阻塞方式实现同​​步行为。它不会重新安排您的逻辑流程;事实上,它竭尽全力保护它。通过在此处异步更改的唯一一件事是,在 2 分钟的数据库操作期间,您不再占用线程,如果您有很多并发用户,这将极大地赢得您的应用程序的可伸缩性,但没有将单个请求加快一点。

    我认为您真正想要的是将操作作为后台作业运行,以便您可以立即响应用户。但要小心 - 在 ASP.NET 中有 bad ways 可以做到这一点(即 Task.Run),还有 good ways

    【讨论】:

      【解决方案2】:

      Dave,您不必在这里使用 await。你是对的 - 从用户的角度来看,它仍然需要 2 分钟。唯一的区别是处理您的请求的线程现在可以处理其他请求,同时数据库完成它的工作。当数据库完成时,线程将继续处理您的请求。

      假设您有有限数量的线程能够处理 HTTP 请求。此异步代码将帮助您在每个时间段处理更多请求,但不会帮助用户更快地完成工作。

      【讨论】:

      • 所以你的意思是,没有解决方案?启动线程的操作,即使在操作完成后仍继续运行?
      • 有解决方案,但首先我们需要了解需求。如果在处理请求后,后台线程由于某种原因失败了怎么办。您将如何通知用户请求未处理,快照未创建?实际上这是“命令”的常见示例,应该快速提交,但是命令的处理可能需要一段时间,并且必须通知用户处理结果。
      • 举个简单的例子:一种方法将命令(“创建快照”)和快照本身保存在数据库中(例如),启动后台线程并将commandId返回给用户。这应该很快。然后我们需要第二种方法,它可以通过给定的 commandId 给我们命令状态,所以“用户”可以使用第二种方法来获取结果。执行命令仍然需要 2 分钟,但用户的 UI 不会冻结,用户可以同时执行其他操作等等。
      • 这个方法暂时不需要给用户反馈(这是另一个问题)。首先我只需要异步触发查询。
      • 如果您的要求如此宽松,您可以简单地使用Task.Run(或ThreadPool.QueueUserWorkItem)开始后台执行并返回响应给用户,而无需等待任务的结果。
      【解决方案3】:

      这似乎是因为对 asyncawait 的作用存在误解。

      async不代表run this on a new thread,本质上它是作为编译器构建状态机的信号,所以这样的方法:

      Task<int> GetMeAnInt()
      {
          return await myWebService.GetMeAnInt();
      }
      

      有点(不能强调这一点),变成这样:

      Task<int> GetMeAnInt()
      {
          var awaiter = myWebService.GetMeAnInt().GetAwaiter();
          awaiter.OnCompletion(() => goto done);
          return Task.InProgress;
          done:
              return awaiter.Result;
      }
      

      MSDN has way more information 关于这个,甚至还有一些代码解释了如何构建自己的等待者。

      asyncawait 的核心只是使您能够编写在后台使用回调的代码,但以一种很好的方式告诉编译器为您完成繁重的工作。

      如果你真的想在后台运行一些东西,那么你需要使用Task

      Task<int> GetMeAnInt()
      {
          return Task.Run(() => myWebService.GetMeAnInt());
      }
      

      Task<int> GetMeAnInt()
      {
          return Task.Run(async () => await myWebService.GetMeAnInt());
      }
      

      第二个示例在 lambda 中使用 async 和 await,因为在这种情况下,Web 服务上的 GetMeAnInt 也恰好返回 Task&lt;int&gt;

      回顾一下:

      1. asyncawait 只是指示编译器做一些 jiggerypokery
        1. 这使用带有goto 的标签和回调
        2. 有趣的是,这是有效的 IL,但 C# 编译器不允许它用于您自己的代码,因此为什么编译器可以摆脱魔法而您却不能。
      2. async 并不意味着“在后台线程上运行”
      3. Task.Run() 可用于将线程池线程排队以运行任意函数
      4. Task.Factory.Start() 可以用来抓取一个全新的线程来运行任意函数
      5. await 指示编译器这是需要awaitable(例如任务)的awaiter 的结果为awaited 的点 - 这就是它知道如何构造状态机的方式。李>

      【讨论】:

      • 谢谢。但是使用线程也不起作用:线程 0x2ac0 已退出,代码为 259 (0x103)。好像是实体框架作用域后线程会被杀死
      • @Dave 您能否编辑问题以包含您使用的导致此异常的代码?
      • 我只是改用Task.Run(() =&gt; db.Database.ExecuteSqlCommandAsync("EXEC PerformSnapshot @User = '" + CurrentUser.AccountName + "', @Comment = '" + snapshot.Comment + "';"));
      • 永远不要在 ASP.NET 应用程序中使用 Task.Run
      • @ToddMenier 我的错,我忘了提这个! Task.Run 在那里演示了它如何与asyncawait 交互。
      【解决方案4】:

      正如我在MSDN article on async ASP.NET 中所描述的,async 不是灵丹妙药;它不会改变 HTTP 协议:

      当一些开发人员了解异步和等待时,他们认为这是服务器代码“让步”给客户端(例如浏览器)的一种方式。但是,ASP.NET 上的 async 和 await 仅“让步”给 ASP.NET 运行时; HTTP 协议保持不变,每个请求仍然只有一个响应。

      在您的情况下,您尝试使用网络请求启动后端操作,然后返回浏览器。 ASP.NET 并不是为执行这样的后端操作而设计的;它只是一个 Web 层框架。让 ASP.NET 执行工作是危险的,因为 ASP.NET 只知道来自其请求的工作。

      我的博客上有overview of various solutions。请注意,使用普通的 Task.RunTask.Factory.StartNewThreadPool.QueueUserWorkItem非常危险,因为 ASP.NET 对这项工作一无所知。 至少您应该使用HostingEnvironment.QueueBackgroundWorkItem,这样 ASP.NET 至少知道这项工作。但这并不能保证这项工作会真正完成。

      一个适当的解决方案是将工作放在一个持久队列中,并有一个独立的后台工作进程来排队。请参阅Asynchronous Messaging Primer(具体来说,您的方案是“解耦工作负载”)。

      【讨论】:

      • 我们不需要该操作的任何结果。我们只想在 DB 级别触发存储过程。我们不关心结果或错误
      • @Dave:你关心程序是否完成吗?当客户端连接意外终止时,数据库往往会变得兴奋并开始回滚所有内容。
      • 不,我不在乎,因为它被封装在事务中,如果过程成功与否,我们会检查其他地方。
      猜你喜欢
      • 2010-09-06
      • 1970-01-01
      • 2022-12-09
      • 1970-01-01
      • 2018-01-18
      • 2013-09-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多