2009-03-23 11 views
46

Tengo una operación asincrónica que por diversos motivos debe activarse mediante una llamada HTTP a una página web ASP.NET. Cuando se solicita mi página, debe comenzar esta operación e inmediatamente devolver un reconocimiento al cliente.Ejecutar una operación asincrónica provocada por una solicitud de página web ASP.NET

Este método también se expone a través de un servicio web WCF, y funciona perfectamente.

En mi primer intento, ha generado una excepción, diciéndome:

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.

Así que por supuesto añadió el parámetro Async="true" a la directiva @Page. Ahora, no obtengo un error, pero la página se está bloqueando hasta que se complete la operación asincrónica.

¿Cómo obtengo una verdadera página de "olvídate del fuego" trabajando?

Editar: Algunos códigos para obtener más información. Es un poco más complicado que esto, pero traté de tener una idea general allí.

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"); 
    } 
} 

La clase 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 
} 
+0

Es difícil de responder sin ver el código que llama a esta operación asíncrona. – Bryan

+0

Ok, actualizaré la pregunta con más información. Gracias. – Damovisa

+0

En Azure, puede usar trabajos web. Consulte http://curah.microsoft.com/52143/using-the-webjobs-feature-of-windows-azure-web-sites Los subprocesos de fondo en ASP.NET son problemáticos. – RickAndMSFT

Respuesta

28

Si no se preocupan por devolver nada al usuario, que sólo puede encender o bien un hilo separado, o para un enfoque rápido y sucio, el uso un delegado e invocarlo de manera asincrónica. Si no le importa notificar al usuario cuando finaliza la tarea asíncrona, puede ignorar la devolución de llamada. Trate de poner un punto de interrupción al final del método SomeVeryLongAction(), y verá que termina de ejecutarse después de la página ya ha sido servida:

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); 
    } 
} 
+0

Gracias, BeginInvoke fue la clave - ¡No estoy seguro de por qué no probé esa! – Damovisa

+2

¡Peligro! Ignorar la devolución de llamada hará que los recursos no se limpien. Esa es una de las razones por las que recomendé un enfoque diferente. Jeffrey Richter habla sobre esto en su CLR a través del libro de C# (es posible que no tenga el título exactamente correcto). –

+3

Es EndInvoke que se debe llamar en algún momento para garantizar la limpieza. Muy cierto. Buena discusión: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/d88d3f1e-f4aa-40c1-b1b6-e79e801f3909/ http://msdn.microsoft.com/en- us/magazine/cc164036 (printer) .aspx –

25

OK, aquí está el problema: el atributo asíncrono es para el caso en el que su página va a llamar a alguna tarea larga que también bloquea el hilo, y luego su página necesita el resultado de esa tarea para devolver información al usuario. Por ejemplo, si su página necesita llamar a un servicio web, espere su respuesta y luego use los datos de la respuesta para representar su página.

La razón por la que usaría el atributo Async es para evitar bloquear el hilo. Esto es importante porque las aplicaciones ASP.NET usan un grupo de subprocesos para atender las solicitudes, y solo hay un número relativamente pequeño de subprocesos disponibles. Y si cada llamada ata el hilo mientras espera la llamada del servicio web, pronto llegará a suficientes usuarios simultáneos que los usuarios tendrán que esperar hasta que se completen estas llamadas al servicio web. El atributo Async permite que el hilo vuelva al grupo de subprocesos y sirva a otros visitantes simultáneos en su sitio web, en lugar de obligarlo a quedarse quieto sin hacer nada mientras espera que vuelva la llamada del servicio web.

El resultado para usted es el siguiente: el atributo Async está diseñado para el caso donde no puede renderizar la página hasta que se complete la tarea asíncrona, y es por eso que no representa la página inmediatamente.

Debe iniciar su propio hilo y convertirlo en un hilo de daemon. No recuerdo la sintaxis exacta para eso, pero puede encontrarlo fácilmente en el documento buscando en el documento BCL "daemon". Esto significa que el hilo evitará que su aplicación se cierre mientras está activa, lo cual es importante porque ASP.NET e IIS se reservan el derecho de "reciclar su proceso" cuando lo consideren necesario, y si eso sucede mientras el hilo está funcionando, tu tarea será detenida Hacer que el demonio de subprocesos lo evite (excepto en algunos casos de borde raros posibles ... descubrirá más cuando encuentre la documentación sobre esto).

Ese hilo daemon es donde iniciará estas tareas. Y después de que hayas indicado el hilo del daemon para realizar la tarea, puedes renderizar tu página de inmediato ... para que el procesamiento de la página se realice de inmediato.

Incluso mejor que un hilo daemon en su proceso ASP.NET, sin embargo, sería implementar un servicio de Windows para realizar la tarea. Haga que su aplicación ASP.NET comunique la tarea que se realizará al Servicio. No es necesario tener un hilo de daemon ni preocuparse por el reciclado de su proceso ASP.NET. ¿Cómo le dices al Servicio que haga la tarea? Tal vez a través de WCF, o tal vez mediante la inserción de un registro en una tabla de base de datos que el Servicio sondea. O una cantidad de otras formas.

EDITAR: Aquí hay otra idea, que he utilizado anteriormente para este mismo propósito. Escriba la información sobre su tarea en una cola de MSMQ. Haga que otro proceso (tal vez incluso en otra máquina) salga de esa cola y realice la tarea que consume mucho tiempo. El trabajo de insertar en una cola está optimizado para regresar lo más rápido posible, por lo que su hilo no se bloqueará mientras los datos que ingresa en la cola se envíen por el cable o algo por el estilo. Es una de las formas más rápidas de tomar nota del hecho de que una tarea debe realizarse sin esperar a que se ejecute.

+0

Excelente respuesta, gracias por eso. De hecho estoy usando MSMQ un poco más adelante. Todo lo que realmente necesitaba hacer era lanzar otro hilo para hacer mi trabajo como me sugeriste. Como mencionaste, el parámetro Async en la directiva @Page no iba a ayudarme. – Damovisa

+0

Bueno, me alegro de que haya ayudado. Por cierto, asegúrese de ver mi comentario sobre la otra respuesta ... ¡ignorar el lado de EndInvoke causará una fuga de recursos!Y la única forma confiable de asegurarse de que pueda procesar el EndInvoke es tener un hilo de daemon. Entonces vuelves a eso o a MSMQ/Servicio. –

+0

Gracias por eso, en realidad reutilicé algún código que hizo algo similar y simplemente no hice nada en EndInvoke. – Damovisa

1

Si obtiene este error al llamar al servicio web de forma asíncrona, asegúrese de agregar el atributo Async = 'true' como se indica en el mensaje de excepción ?

superior de la página < Page Language = 'VB' asíncrono = 'verdadero' AutoEventWireup = 'false' CodeFile = 'mynewpage.aspx.vb' hereda 'MyNewPage' =%>

+0

no se olvide de marcar como ans si ayudó – Amrik

+3

La respuesta a esta pregunta fue aceptada hace 3 años. También se mencionó Async = verdadero en la pregunta original. Bienvenido a SO. –

+0

@nathanchere si miras de cerca, respondí esto en 2012. ¿Tuviste la oportunidad de votar abajo quién copió mi respuesta en 2013 y aceptó 14 veces? – Amrik

2

puede solucionar esta limitación es bastante fácil y sin configurar Async en true.

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

formularios web Si está ejecutando establecen Ansync = "true" en la página .aspx en el que se realiza la solicitud.
<%@ Page Language="C#" Async="true" ... %>

Cuestiones relacionadas