2009-01-24 13 views
7

Tengo una biblioteca de terceros que contiene una clase que realiza una función de forma asíncrona. La clase hereda del formulario. La función básicamente realiza un cálculo basado en datos almacenados en una base de datos. Una vez que ha finalizado, llama a un evento _Complete en el formulario de llamada.Cómo envolver un método asíncrono de forma síncrona en C#

Lo que me gustaría hacer es llamar a la función de forma sincrónica, pero desde una aplicación de formulario que no sea Windows. El problema es que, sin importar lo que haga, mis bloques de aplicaciones y el controlador de eventos _Complete nunca se activan. Desde un formulario de Windows, puedo simular la función ejecutando sincrónicamente usando un indicador "completo" y un "while (! Complete) application.doevents", pero obviamente application.doevents no está disponible en una aplicación de formulario que no sea Windows.

¿Hay algo que me impida usar el método de la clase fuera de una aplicación de formulario de Windows (debido a que hereda de 'Form')? ¿Hay alguna manera de solucionar este problema?

Gracias, Mike

+0

No estoy seguro. ¿Tu código se ejecuta correctamente cuando se ejecuta de forma asíncrona en una aplicación que no es de Windows? ¿O siempre se bloquea indefinidamente en una aplicación que no es Windows? – Sam

+0

En una aplicación que no es Windows, bloquea indefinidamente porque el evento _Complete nunca se dispara, a pesar de intentar ManualResetEvents, etc. En una aplicación de formulario de Windows, funciona bien. – mikecamimo

Respuesta

0

¿Tiene la fuente para el componente? Parece que se basa en el hecho de que se llamará desde un entorno WinForms (debe ser una buena razón por la que una biblioteca hereda de Form!), Pero es difícil saberlo con certeza.

8

En una puñalada, podría valer la pena intentar algo como lo siguiente que utiliza un WaitHandle para bloquear el hilo actual en lugar de girar y comprobar un indicador.

using System; 
using System.Threading; 

class Program 
{ 
    AutoResetEvent _autoEvent; 

    static void Main() 
    { 
     Program p = new Program(); 
     p.RunWidget(); 
    } 

    public Program() 
    { 
     _autoEvent = new AutoResetEvent(false); 
    } 

    public void RunWidget() 
    { 
     ThirdParty widget = new ThirdParty();   
     widget.Completed += new EventHandler(this.Widget_Completed); 
     widget.DoWork(); 

     // Waits for signal that work is done 
     _autoEvent.WaitOne(); 
    } 

    // Assumes that some kind of args are passed by the event 
    public void Widget_Completed(object sender, EventArgs e) 
    { 
     _autoEvent.Set(); 
    } 
} 
+0

Creo que solo lo hizo dentro de un arnés de prueba WinForms (que funcionó), creo que el problema es que la operación nunca termina cuando no está en una aplicación de Winforms, por lo que realmente no importa cómo esperas si nunca es va a terminar :) –

+0

Sospecho que puede ser el caso, pero vale la pena señalar la técnica. Nunca sabes. :) – Kev

+1

Claro ... una persona menos sentada en un bucle while martillando la CPU sin ninguna razón es algo bueno :-D –

1

Tengo más información sobre este problema (estoy trabajando en el mismo equipo que mikecamimo).

El problema también ocurre en la aplicación Windows Forms, cuando se replica correctamente. En el OP original, el problema no ocurría en el formulario de Windows porque no había bloqueo. Cuando se introduce el bloqueo mediante el uso de un ResetEvent, ocurre el mismo problema.

Esto se debe a que el controlador de eventos (Widget_completado) está en el mismo hilo que el método que llama a Widget.DoWork. El resultado es que AutoResetEvent.WaitOne(); bloquea para siempre porque nunca se llama al controlador de eventos para establecer el evento.

En un entorno de formularios de Windows esto puede solucionarse utilizando Application.DoEvents para sondear la cola de mensajes y permitir que el evento se maneje. Vea abajo.

using System; 
using System.Threading; 
using System.Windows.Forms; 

class Program 
{ 
    EventArgs data; 

    static void Main() 
    { 
     Program p = new Program(); 
     p.RunWidget(); 
    } 

    public Program() 
    { 
     _autoEvent = new AutoResetEvent(false); 
    } 

    public void RunWidget() 
    { 
     ThirdParty widget = new ThirdParty();     
     widget.Completed += new EventHandler(this.Widget_Completed); 
     data = null; 
     widget.DoWork(); 

     while (data == null); 
      Application.DoEvents(); 

     // do stuff with the results of DoWork that are contained in EventArgs. 
    } 

    // Assumes that some kind of args are passed by the event 
    public void Widget_Completed(object sender, EventArgs e) 
    { 
     data = e; 
    } 
} 

de una manera no aplicación de Windows Forms, tales como un servicio de Windows, la aplicación no está disponible para que DoEvents no pueden ser llamados.

El problema es uno de threading y ese widget.El controlador de eventos asociado de DoWork de alguna manera tiene que estar en otra secuencia. Esto debería evitar que AutoResetEvent.WaitOne bloquee indefinidamente. Creo ... :)

Cualquier idea sobre cómo lograr esto sería fantástica.

+0

Lo sé hace mucho tiempo, pero podrías intentar ejecutar el código de terceros en un hilo diferente, es decir, iniciar explícitamente un nuevo hilo para ejecutarlo. De esta forma, WaitOne bloqueará en un hilo diferente, y el evento Completado lo hará (debería) disparar. – Schmuli

1
AutoResetEvent _autoEvent = new AutoResetEvent(false); 

public WebBrowser SyncronNavigation(string url) 
{ 
    WebBrowser wb = null; 

    wb = new WebBrowser(); 
    wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(wb_DocumentCompleted); 

    wb.ScriptErrorsSuppressed = true; 
    wb.Navigate(new Uri(url)); 

    while (!_autoEvent.WaitOne(100)) 
     Application.DoEvents(); 

    return wb; 
} 

void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) 
{ 
    //throw new NotImplementedException(); 
    _autoEvent.Set(); 
} 
Cuestiones relacionadas