我有一个异步操作,由于各种原因需要使用对ASP.NET网页的HTTP调用来触发.当我的页面被请求时,它应该开始此操作并立即向客户端返回确认.
此方法也通过WCF Web服务公开,并且完美地运行.
在我第一次尝试时,抛出异常,告诉我:
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.
所以我当然把Async="true"
参数添加到@Page
指令中.现在,我没有收到错误,但页面阻塞,直到异步操作完成.
如何让真正的"即发即弃"页面正常工作?
编辑: 一些代码以获取更多信息.它比这复杂一点,但我试图在那里得到一般的想法.
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"); } }
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 }
vikingben.. 32
如果您正在运行webforms,请在您发出请求的.aspx页面中设置Ansync ="true".
<%@ Page Language="C#" Async="true" ... %>
如果您正在运行webforms,请在您发出请求的.aspx页面中设置Ansync ="true".
<%@ Page Language="C#" Async="true" ... %>
如果你不关心向用户返回任何东西,你可以启动一个单独的线程,或者为了快速和脏的方法,使用委托并异步调用它.如果您不关心在异步任务完成时通知用户,则可以忽略回调.尝试在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); } }
好的,这就是问题所在:Async属性适用于您的页面将调用一些长时间运行的任务同时阻塞该线程的情况,然后您的页面需要该任务的输出才能将信息返回给用户.例如,如果您的页面需要调用Web服务,请等待其响应,然后使用响应中的数据呈现您的页面.
您使用Async属性的原因是为了避免阻塞线程.这很重要,因为ASP.NET应用程序使用线程池来处理请求,并且只有相对较少的线程可用.如果每个调用在等待Web服务调用时占用线程,那么很快你就会遇到足够的并发用户,用户将不得不等到这些Web服务调用完成.Async属性允许线程返回到线程池并为您的网站提供其他并发访问者,而不是在等待Web服务调用返回时强制它静止不动.
您的结果是:Async属性是针对在异步任务完成之前无法呈现页面的情况而设计的,这就是它不会立即呈现页面的原因.
您需要启动自己的线程,并使其成为守护程序线程.我不记得确切的语法,但您可以通过在BCL doc中搜索"守护进程"轻松地在文档中找到它.这意味着该线程将使您的应用程序在活动时不会关闭,这很重要,因为ASP.NET和IIS保留在他们认为必要时"回收您的进程"的权利,并且如果在您的线程工作时发生这种情况,你的任务将被停止.制作线程守护进程将阻止这种情况(除了一些可能的罕见边缘情况......当你找到关于此的文档时,你会发现更多).
该守护程序线程是您启动这些任务的地方.在您告诉守护程序线程执行任务之后,您可以立即呈现页面...因此页面的呈现将立即发生.
但是,与ASP.NET进程中的守护程序线程相比,更好的方法是实现Windows服务来执行任务.让您的ASP.NET应用程序将要执行的任务传达给服务.不需要守护程序线程,也不必担心您的ASP.NET进程被回收.您如何告诉服务部门完成任务?也许通过WCF,或者可能通过将记录插入服务轮询的数据库表中.或者其他一些方式.
编辑:这是另一个想法,我之前用过这个目的.将有关任务的信息写入MSMQ队列.有另一个进程(甚至可能在另一台机器上)从该队列中拉出并执行耗时的任务.插入队列的工作经过优化,可以尽快返回,因此当您放入队列中的数据通过网络或类似的方式发送时,您的线程不会阻塞.这是记录任务需要完成而无需等待该任务执行的事实的最快方法之一.