C# 运行由 ASP.NET 网页请求触发的异步操作
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/672237/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Running an asynchronous operation triggered by an ASP.NET web page request
提问by Damovisa
I have an asynchronous operation that for various reasons needs to be triggered using an HTTP call to an ASP.NET web page. When my page is requested, it should start this operation and immediately return an acknowledgment to the client.
我有一个异步操作,出于各种原因需要使用对 ASP.NET 网页的 HTTP 调用来触发。当我的页面被请求时,它应该开始这个操作并立即向客户端返回一个确认。
This method is also exposed via a WCF web service, and it works perfectly.
此方法也通过 WCF Web 服务公开,并且运行良好。
On my first attempt, an exception was thrown, telling me:
在我第一次尝试时,抛出了一个异常,告诉我:
Asynchronous operations are not allowed in this context. Page starting an asynchronous operation has to have the Async attribute set to true and an asynchronous operation can only be started on a page prior to PreRenderComplete event.
So of course I added the Async="true"
parameter to the @Page
directive. Now, I'm not getting an error, but the page is blocking until the Asynchronous operation completes.
所以当然我将Async="true"
参数添加到@Page
指令中。现在,我没有收到错误消息,但页面在异步操作完成之前一直处于阻塞状态。
How do I get a true fire-and-forget page working?
如何让真正的即发即忘页面正常工作?
Edit:Some code for more info. It's a bit more complicated than this, but I've tried to get the general idea in there.
编辑:一些代码以获取更多信息。它比这更复杂一点,但我试图在那里得到一般的想法。
public partial class SendMessagePage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string message = Request.QueryString["Message"];
string clientId = Request.QueryString["ClientId"];
AsyncMessageSender sender = new AsyncMessageSender(clientId, message);
sender.Start();
Response.Write("Success");
}
}
The AsyncMessageSender class:
AsyncMessageSender 类:
public class AsyncMessageSender
{
private BackgroundWorker backgroundWorker;
private string client;
private string msg;
public AsyncMessageSender(string clientId, string message)
{
this.client = clientId;
this.msg = message;
// setup background thread to listen
backgroundThread = new BackgroundWorker();
backgroundThread.WorkerSupportsCancellation = true;
backgroundThread.DoWork += new DoWorkEventHandler(backgroundThread_DoWork);
}
public void Start()
{
backgroundThread.RunWorkerAsync();
}
...
// after that it's pretty predictable
}
采纳答案by Ilya Tchivilev
If you don't care about returning anything to the user, you can just fire up either a separate thread, or for a quick and dirty approach, use a delegate and invoke it asynchrnously. If you don't care about notifying the user when the async task finishes, you can ignore the callback. Try putting a breakpoint at the end of the SomeVeryLongAction() method, and you'll see that it finishes running after the page has already been served up:
如果你不关心向用户返回任何东西,你可以启动一个单独的线程,或者为了快速而肮脏的方法,使用委托并异步调用它。如果您不关心在异步任务完成时通知用户,则可以忽略回调。尝试在 SomeVeryLongAction() 方法的末尾放置一个断点,您会看到它在页面已经被提供后完成运行:
private delegate void DoStuff(); //delegate for the action
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
//create the delegate
DoStuff myAction = new DoStuff(SomeVeryLongAction);
//invoke it asynchrnously, control passes to next statement
myAction.BeginInvoke(null, null);
Button1.Text = DateTime.Now.ToString();
}
private void SomeVeryLongAction()
{
for (int i = 0; i < 100; i++)
{
//simulation of some VERY long job
System.Threading.Thread.Sleep(100);
}
}
回答by Charlie Flowers
OK, here's the problem: the Async attribute is for the case where your page is going to call some long-running task that also blocks the thread, and then your page needs the output from that task in order to return info to the user. For example, if your page needed to call a web service, wait for its response, and then use the data from the response to render your page.
好的,问题来了:Async 属性适用于以下情况:您的页面将调用一些也会阻塞线程的长时间运行的任务,然后您的页面需要该任务的输出以便将信息返回给用户。例如,如果您的页面需要调用 Web 服务,请等待其响应,然后使用响应中的数据来呈现您的页面。
The reason you'd use the Async attribute is to avoid blocking the thread. This is important because ASP.NET applications use a thread pool to serve requests, and there are only a relatively small number of threads available. And if each call ties up the thread while waiting on the web service call, then soon you're going to hit enough concurrent users that users are going to have to wait until these web service calls complete. The Async attribute lets the thread return to the thread pool and serve other concurrent visitors to your web site, rather than forcing it to sit still doing nothing while waiting for the web service call to return.
您使用 Async 属性的原因是为了避免阻塞线程。这很重要,因为 ASP.NET 应用程序使用线程池来为请求提供服务,并且可用的线程数量相对较少。如果在等待 Web 服务调用时每个调用都占用了线程,那么很快您就会遇到足够多的并发用户,用户将不得不等待这些 Web 服务调用完成。Async 属性让线程返回到线程池并为您网站的其他并发访问者提供服务,而不是强迫它在等待 Web 服务调用返回时静坐不动。
The upshot for you is this: the Async attribute is designed for the case where you can't render the page until the asynchronous task completes, and that's why it doesn't render the page immediately.
您的结果是这样的:Async 属性是为在异步任务完成之前您无法呈现页面的情况而设计的,这就是它不会立即呈现页面的原因。
You need to launch your own thread, and make it a daemon thread. I don't remember the exact syntax for that, but you can easily find it in the doc by searching the BCL doc for "daemon". This means the thread will keep your application from shutting down while it is alive, which is important because ASP.NET and IIS reserve the right to "recycle your process" when they deem it necessary, and if that happens while your thread is working, your task will be stopped. Making the thread daemon will prevent this (except for some possible rare edge cases ... you'll find out more when you find the documentation on this).
您需要启动自己的线程,并使其成为守护线程。我不记得它的确切语法,但是您可以通过在 BCL 文档中搜索“守护进程”轻松地在文档中找到它。这意味着线程将阻止您的应用程序在它处于活动状态时关闭,这很重要,因为 ASP.NET 和 IIS 保留在他们认为必要时“回收您的进程”的权利,如果在您的线程工作时发生这种情况,你的任务将被停止。制作线程守护进程将阻止这种情况(除了一些可能的罕见边缘情况……当您找到有关此的文档时,您会发现更多信息)。
That daemon thread is where you will kick off these tasks. And after you've told the daemon thread to do the task, you can immediately render your page ... so the rendering of the page will happen immediately.
该守护进程线程是您开始这些任务的地方。在您告诉守护线程完成任务后,您可以立即呈现您的页面……因此页面的呈现将立即发生。
Even better than a daemon thread in your ASP.NET process, though, would be to implement a Windows Service for doing the task. Have your ASP.NET application communicate the task to be performed to the Service. No need for a daemon thread and no need to worry about your ASP.NET process being recycled. How do you tell the Service to do the task? Perhaps through WCF, or perhaps by inserting a record into a database table that the Service polls. Or a number of other ways.
但是,比 ASP.NET 进程中的守护线程更好的是实现 Windows 服务来完成任务。让您的 ASP.NET 应用程序将要执行的任务传达给服务。不需要守护线程,也不需要担心您的 ASP.NET 进程被回收。你如何告诉服务完成任务?也许通过 WCF,或者也许通过将记录插入到服务轮询的数据库表中。或者其他一些方式。
EDIT: Here's another idea, which I have used before for this very same purpose. Write the info about your task into an MSMQ queue. Have another process (maybe even on another machine) pull from that queue and do the time-consuming task. The job of inserting into a Queue is optimized to return as quickly as possible, so your thread won't block while the data you put in the Queue is sent across the wire or anything like that. It is one of the fastest ways to make note of the fact that a task needs to be done without waiting for that task to execute.
编辑:这是另一个想法,我以前曾用它来达到同样的目的。将有关您的任务的信息写入 MSMQ 队列。让另一个进程(甚至可能在另一台机器上)从该队列中提取并执行耗时的任务。插入队列的工作经过优化以尽快返回,因此当您放入队列的数据通过线路或类似方式发送时,您的线程不会阻塞。这是记录任务需要完成而无需等待该任务执行这一事实的最快方法之一。
回答by Amrik
If you get this error when calling web service asynchronously, make sure adding the Async='true' attribute as instructed by the exception message?
如果在异步调用 Web 服务时出现此错误,请确保按照异常消息的指示添加 Async='true' 属性?
top of the page < Page Language='VB' Async='true' AutoEventWireup='false' CodeFile='mynewpage.aspx.vb' Inherits='mynewpage' %>
页面顶部 <Page Language='VB' Async='true' AutoEventWireup='false' CodeFile='mynewpage.aspx.vb' Inherits='mynewpage' %>
回答by Serge
You can work around this limitation quite easily and without even setting Async to true.
您可以非常轻松地解决此限制,甚至无需将 Async 设置为 true。
public void Start()
{
new Task(() =>
{
backgroundThread.RunWorkerAsync();
}).Start();
}
回答by vikingben
If you are running webforms set Ansync = "true" in your .aspx page where you are making the request. <%@ Page Language="C#" Async="true" ... %>
如果您正在运行网络表单,请在您发出请求的 .aspx 页面中设置 Ansync = "true"。 <%@ Page Language="C#" Async="true" ... %>