异步数据库查询触发存储过程

Asynchronous DB-Query to trigger Stored Procedure

我想在 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());
}

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

所以...为什么我必须在这里等待?

或者方法是否会启动,但如果请求完成则终止?

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

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

这似乎是对 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 也恰好 [=7​​1=] Task<int>.

回顾一下:

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

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

When some developers learn about async and await, they believe it’s a way for the server code to “yield” to the client (for example, the browser). However, async and await on ASP.NET only “yield” to the ASP.NET runtime; the HTTP protocol remains unchanged, and you still have only one response per request.

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

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

一个正确的解决方案是将工作放在一个持久队列中,并让一个独立的后台工作进程排队。请参阅 Asynchronous Messaging Primer(具体来说,您的方案是 "Decoupling workloads")。

We don't want the user to wait that long.

async-await 帮不了你。听起来可能很奇怪,基本 async-await 模式是关于 以非阻塞方式 实现同步 行为 。它不会重新安排您的逻辑流程;事实上,它竭尽全力保护它。在这里通过异步改变的唯一一件事是,在 2 分钟的数据库操作期间,您不再占用一个线程,如果您有很多并发用户,那么这对您的应用程序的可扩展性来说是一个巨大的胜利,但没有将单个请求加速一位。

我认为您真正想要的是 运行 将操作作为后台作业,以便您可以立即响应用户。但要小心 - 有 bad ways to do that in ASP.NET (i.e. Task.Run) and there are good ways.