运行由ASP.NET网页请求触发的异步操作

时间:2009-03-23 04:28:32

标签: c# asp.net asynchronous

我有一个异步操作,由于各种原因需要使用对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
}

5 个答案:

答案 0 :(得分:31)

如果您不关心向用户返回任何内容,您可以启动一个单独的线程,或者为了快速而肮脏的方法,使用委托并异步调用它。如果您不关心在异步任务完成时通知用户,则可以忽略回调。尝试在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);
    }
}

答案 1 :(得分:30)

如果您正在运行webforms,请在您发出请求的.aspx页面中设置Ansync =“true”。
<%@ Page Language="C#" Async="true" ... %>

答案 2 :(得分:26)

好的,问题在于:Async属性适用于您的页面将调用一些长时间运行的任务同时阻塞该线程的情况,然后您的页面需要该任务的输出才能将信息返回给用户。例如,如果您的页面需要调用Web服务,请等待其响应,然后使用响应中的数据呈现您的页面。

您使用Async属性的原因是为了避免阻塞线程。这很重要,因为ASP.NET应用程序使用线程池来处理请求,并且只有相对较少的线程可用。如果每次调用都在等待Web服务调用时占用线程,那么很快就会遇到足够多的并发用户,用户将不得不等到这些Web服务调用完成。 Async属性允许线程返回到线程池并为您的网站提供其他并发访问者,而不是强迫它在等待Web服务调用返回时仍然无所事事。

您的结果是:Async属性是针对在异步任务完成之前无法呈现页面的情况而设计的,这就是它不会立即呈现页面的原因。

您需要启动自己的线程,并使其成为守护程序线程。我不记得确切的语法,但您可以通过在BCL doc中搜索“守护进程”轻松地在文档中找到它。这意味着该线程将使您的应用程序在活动时不会关闭,这很重要,因为ASP.NET和IIS保留在他们认为必要时“回收您的进程”的权利,并且如果在您的线程工作时发生这种情况,你的任务将被停止。制作线程守护进程将阻止这种情况(除了一些可能的罕见边缘情况......当你找到关于此的文档时,你会发现更多)。

该守护程序线程是您启动这些任务的地方。在你告诉守护程序线程完成任务之后,你可以立即渲染你的页面......所以页面的渲染会立即发生。

然而,比ASP.NET进程中的守护程序线程更好的方法是实现Windows服务来执行任务。让您的ASP.NET应用程序将要执行的任务传达给服务。不需要守护程序线程,也不必担心您的ASP.NET进程被回收。您如何告诉服务部门完成任务?也许通过WCF,或者可能通过将记录插入服务轮询的数据库表中。或者其他一些方式。

编辑:这是另一个想法,我之前用过这个目的。将有关任务的信息写入MSMQ队列。有另一个进程(甚至可能在另一台机器上)从该队列中拉出并执行耗时的任务。插入队列的工作经过优化,可以尽快返回,因此当您放入队列中的数据通过网络或类似的方式发送时,您的线程不会阻塞。这是记录任务需要在不等待执行任务的情况下完成任务的最快方法之一。

答案 3 :(得分:3)

您可以非常轻松地解决此限制,甚至无需将Async设置为true。

public void Start()
{
    new Task(() =>
    {
        backgroundThread.RunWorkerAsync();
    }).Start();
}

答案 4 :(得分:1)

如果在异步调用Web服务时出现此错误,请确保按照指示添加Async ='true'属性 异常消息?

页面顶部&lt; Page Language ='VB'Async ='true'AutoEventWireup ='false' CodeFile ='mynewpage.aspx.vb'Insertits ='mynewpage'%&gt;