2009-01-28 20 views
9

Digamos que tengo un componente llamado Tarea (que no puedo modificar) que expone un método "DoTask" que hace algunos cálculos posiblemente largos y devuelve el resultado a través de un evento TaskCompleted. Normalmente esto se llama en una forma de Windows que el usuario cierra después de que obtiene los resultados.¿Es posible colocar un controlador de eventos en un hilo diferente para la persona que llama?

En mi escenario particular que necesita asociar algunos datos (un registro de la base) con los datos devueltos en TaskCompleted y usarlo para actualizar el registro de base de datos.

que he investigado el uso de AutoResetEvent para notificar cuando se maneja el evento. El problema con eso es AutoResetEvent.WaitOne() se bloqueará y el controlador de eventos nunca se llamará. Normalmente AutoResetEvents se llama ser un hilo separado, así que supongo que eso significa que el controlador de eventos está en el mismo hilo que el método que llama.

Básicamente quiero convertir una llamada asincrónica, donde los resultados se devuelven mediante un evento, en una llamada síncrona (es decir, llamar a DoSyncTask desde otra clase) bloqueando hasta que se maneje el evento y los resultados colocados en una ubicación accesible tanto el controlador de eventos como el método que invocó el método que inició la llamada asincrónica.

public class SyncTask 
{ 
    TaskCompletedEventArgs data; 
    AutoResetEvent taskDone; 

public SyncTask() 
{ 
    taskDone = new AutoResetEvent(false); 
} 

public string DoSyncTask(int latitude, int longitude) 
{ 
    Task t = new Task(); 
    t.Completed = new TaskCompletedEventHandler(TaskCompleted); 
    t.DoTask(latitude, longitude); 
    taskDone.WaitOne(); // but something more like Application.DoEvents(); in WinForms. 
    taskDone.Reset(); 
    return data.Street; 
} 

private void TaskCompleted(object sender, TaskCompletedEventArgs e) 
{ 
    data = e; 
    taskDone.Set(); //or some other mechanism to signal to DoSyncTask that the work is complete. 
} 
} 

In a Windows App the following works correctly. 

public class SyncTask 
{ 
    TaskCompletedEventArgs data; 

public SyncTask() 
{ 
    taskDone = new AutoResetEvent(false); 
} 

public string DoSyncTask(int latitude, int longitude) 
{ 
    Task t = new Task(); 
    t.Completed = new TaskCompletedEventHandler(TaskCompleted); 
    t.DoTask(latitude, longitude); 
    while (data == null) Application.DoEvents(); 

    return data.Street; 
} 

private void TaskCompleted(object sender, TaskCompletedEventArgs e) 
{ 
    data = e; 
} 
} 

que sólo tiene que repetir ese comportamiento en un servicio de ventana, donde Application.Run no se llama y el objeto Application Context no está disponible.

+0

Aquí hay un buen video para entender 'AutoResetEvent' http://www.youtube.com/watch?v=xaaRBh07N34 – Jaider

Respuesta

2

Tal vez usted podría conseguir DoSyncTask para iniciar un objeto de temporizador que comprueba el valor de la variable de datos en un cierto intervalo apropiado. Una vez que los datos tienen un valor, puede hacer que otro evento se dispare para decirle que los datos ahora tienen un valor (y apague el temporizador, por supuesto).

Bastante feo truco, pero podría funcionar ... en teoría.

Lo sentimos, eso es lo mejor que puedo llegar a medio dormido. Hora de irse a la cama ...

+0

Parece que podría funcionar. Como dijiste, muy "hacky" :) –

0

Si la tarea es un componente de WinForms, puede ser muy consciente de los problemas del hilo e invocar el controlador de eventos en el hilo principal, que parece ser lo que está viendo.

Por lo tanto, podría ser que se basa en un suministro de mensajes pasando o algo así. Application.Run tiene sobrecargas que son para aplicaciones que no son de GUI. Puede considerar obtener un hilo para el inicio y la bomba para ver si eso soluciona el problema.

También me gustaría recomendar el uso de reflector para obtener un vistazo al código fuente del componente de averiguar lo que está haciendo.

0

Ya casi lo tienes. Necesita que el método DoTask se ejecute en un hilo diferente para que la llamada WaitOne no evite que se realice el trabajo. Algo como esto:

Action<int, int> doTaskAction = t.DoTask; 
doTaskAction.BeginInvoke(latitude, longitude, cb => doTaskAction.EndInvoke(cb), null); 
taskDone.WaitOne(); 
+0

Parece prometedor. Lo probaré mañana y votaré en consecuencia :) –

+0

Desafortunadamente, si conecto el controlador de eventos en el hilo actual (como el anterior) y llamo a DoTask en un hilo diferente, el controlador de eventos sigue bloqueado con taskDone.WaitOne() ; –

+0

¿Es esto algún tipo de componente COM? Cree su instancia en el hilo de trabajo para que no intente obtener el hilo STA. –

2

que elaboró ​​una solución al asíncrono para sincronizar problema, al menos usando todas las clases de .NET.

http://geekswithblogs.net/rgray/archive/2009/01/29/turning-an-asynchronous-call-into-a-synchronous-call.aspx

Todavía no funciona con COM. Sospecho que debido a STA enhebrando. El evento generado por el componente .NET que aloja el COM OCX nunca es manejado por mi subproceso de trabajo, por lo que me sale un punto muerto en WaitOne().

otra persona puede apreciar la solución aunque :)

+0

¿No sería cortés resumir la solución aquí en SO, usando las mismas simplificaciones que en la pregunta? – jwg

0

Mi comentario sobre la respuesta de Scott W parece un poco críptica después de releerlo. Así que permítanme ser más explícito:

while(!done) 
{ 
    taskDone.WaitOne(200); 
    Application.DoEvents(); 
} 

El WaitOne (200) hará que se devuelve el control a su hilo de interfaz de usuario de 5 veces por segundo (se puede ajustar esta como desee). La llamada DoEvents() limpiará la cola de eventos de Windows (la que maneja todo el manejo de eventos de Windows como pintar, etc.). Agregue dos miembros a su clase (una bandera bool "hecho" en este ejemplo, y una "calle" de datos de retorno en su ejemplo).

Esa es la forma más sencilla de hacer lo que quiere. (Tengo un código muy similar en una aplicación propia, así que sé que funciona)

+0

¡Oh! Además, el valor de retorno de WaitOne puede indicarle si se devolvió porque se agotó el tiempo o se indicó. Puedes usar esto para "hecho". – dviljoen

+0

No lo haría de esa manera. Para obtener más información acerca de mis inquietudes, consulte http://blogs.msdn.com/jfoscoding/archive/2005/08/06/448560.aspx – ollifant

+0

Problema con Application.DoEvents es que necesita una aplicación, que solo está disponible para las aplicaciones de WinForms. Llamar a la aplicación. Ejecutar etc. en una biblioteca de clases simplemente no funciona. –

3

Últimamente he tenido algunos problemas para realizar llamadas asincrónicas y eventos en los hilos y devolverlos al hilo principal.

Usé SynchronizationContext para realizar un seguimiento de las cosas. El (pseudo) código a continuación muestra lo que funciona para mí en este momento.

SynchronizationContext context; 

void start() 
{ 
    //First store the current context 
    //to call back to it later 
    context = SynchronizationContext.Current; 

    //Start a thread and make it call 
    //the async method, for example: 
    Proxy.BeginCodeLookup(aVariable, 
        new AsyncCallback(LookupResult), 
        AsyncState); 
    //Now continue with what you were doing 
    //and let the lookup finish 
} 

void LookupResult(IAsyncResult result) 
{ 
    //when the async function is finished 
    //this method is called. It's on 
    //the same thread as the the caller, 
    //BeginCodeLookup in this case. 
    result.AsyncWaitHandle.WaitOne(); 
    var LookupResult= Proxy.EndCodeLookup(result); 
    //The SynchronizationContext.Send method 
    //performs a callback to the thread of the 
    //context, in this case the main thread 
    context.Send(new SendOrPostCallback(OnLookupCompleted), 
       result.AsyncState);       
} 

void OnLookupCompleted(object state) 
{ 
    //now this code will be executed on the 
    //main thread. 
} 

Espero que esto ayude, ya que me solucionó el problema.

0

su código es casi justo ... Me acaba de cambiar

t.DoTask(latitude, longitude); 

para

new Thread(() => t.DoTask(latitude, longitude)).Start(); 

TaskCompleted se ejecutará en la misma rosca que DoTask hace. Esto debería funcionar.

Cuestiones relacionadas