2010-02-12 25 views
11

Tenemos este escenario común en el que tenemos un método que realiza alguna acción asincrónicamente y plantea un evento cuando está hecho.¿Hay una manera genérica de sincronizar un método asincrónico?

Hay momentos en los que queremos que se haga de forma sincrónica en lugar así que tenemos código que es similar a esto:

ManualResetEvent reset = new ManualResetEvent(false); 
someobject.AsyncActionDone += (sender, args) => reset.Set(); 
someobject.PerformAsyncAction(); 
reset.WaitOne(); 

¿Hay una manera de escribir un método de ayuda para hacer esto? Puedo pasar la acción para realizarla, pero no estoy seguro de cómo pasar algo que permita que el método de ayuda sepa qué evento escuchar, ya que no parece que se pueda pasar en un EventHandler como parámetro.

Preferiblemente una solución que no requiere reflexión

Parece haber cierta confusión, esto es una muestra de lo que está someObject clase es como:

public class SomeClass 
{ 
    private ExternalServer someServerOverTheNetwork = new ExternalServer(); 

    public event EventHandler AsyncActionDone; 
    public Data SomeData { get; set; } 
    public void PerformAsyncAction() 
    { 
     someServerOverTheNetwork.GetSomeData(OnDataRetrived); 
    } 

    public Data OnDataRetrived(Data someData) 
    { 
     AsyncActionDone(this, new DataEventArgs(someData)); 
    } 
} 
+0

¿Está el implementador de someObject o se trata de un tipo de biblioteca que no se puede modificar? –

+0

Somos, sin embargo, hay muchos tales objetos. La mayoría de las acciones implican el envío de una solicitud a un servidor y la espera de que el resultado regrese, por lo tanto, por qué son asíncronas por defecto. – Davy8

+0

Tenemos un método para hacer esto para una de nuestras acciones más comunes, almacenar en caché una lista de objetos del servidor, sin embargo eso depende de saber el tipo exacto de objeto y el evento para escuchar. Me gustaría poder pasar en qué evento escuchar para que podamos reducir el código duplicado, ya que las únicas cosas que difieren son el método y el evento que se genera al finalizar. – Davy8

Respuesta

4

Consideraría implementar Asynchronous Design Pattern en los objetos que realizan una operación asíncrona.

public object Operation(object arg) 
{ 
    var ar = BeginOperation(arg, null, null); 

    return EndOperation(ar); 
} 

public IAsyncResult BeginOperation(object arg, AsyncCallback asyncCallback, object state) 
{ 
    AsyncResult asyncResult = new AsyncResult(asyncCallback, state); 

    // Lauch the asynchronous operation 

    return asyncResult; 
} 

private void LaunchOperation(AsyncResult asyncResult) 
{ 
    // Do something asynchronously and call OnOperationFinished when finished 
} 

private void OnOperationFinished(AsyncResult asyncResult, object result) 
{ 
    asyncResult.Complete(result); 
} 


public object EndOperation(IAsyncResult asyncResult) 
{ 
    AsyncResult ar = (AsyncResult)asyncResult; 

    return ar.EndInvoke(); 
} 

Con este patrón tiene la flexibilidad de tener múltiples operaciones asincrónicas simultáneas en su objeto.

Nota: Puede encontrar fácilmente una implementación de una clase AsyncResult genérica en la web.

Editar:

Como desea mantener el diseño actual, si todo el objeto sólo puede tener una operación asíncrona, entonces se podría definir una interfaz IAsyncOperation e implementarlo en toda su objeto.

public interface IAsyncOperation 
{ 
    event EventHandler AsyncActionDone; 
    void PerformAsyncAction(); 
} 

entonces usted podría tener:

public static CallSynchronously(IAsyncOperation asyncOperation) 
{ 
    ManualResetEvent reset = new ManualResetEvent(false); 
    asyncOperation.AsyncActionDone += (sender, args) => reset.Set(); 
    asyncOperation.PerformAsyncAction(); 
    reset.WaitOne(); 
} 

Si sus objetos pueden contener operación asíncrona múltiples, a continuación, sin reflexión Creo que no hay manera de lograr lo que quiere hacer, pero todavía se podría definir una versión síncrona de todas las operaciones asíncronas que envuelve el ManualResetEvent.

public void PerformAction() 
{ 
    ManualResetEvent reset = new ManualResetEvent(false); 
    this.AsyncActionDone += (sender, args) => reset.Set(); 
    this.PerformAsyncAction(); 
    reset.WaitOne(); 
} 
+0

Estoy buscando una solución que no implique modificar la clase de 'someobject' El patrón actual ya existe en docenas de clases en nuestro sistema. – Davy8

+0

Pregunta editada con suerte una mejor explicación – Davy8

+0

Editó la respuesta también –

3

Si le cojo su deriva, se puede basta con utilizar delegados de la siguiente manera, crear un delegado para usted función de llamada, permite llamarlo

public delegate string AsyncDelegate(); 

a continuación, crear un proxy de func ion de la siguiente manera:

public static void ExecuteSync(AsyncDelegate func) 
    { 
     ManualResetEvent reset = new ManualResetEvent(false); 
     int threadId; 
     func.BeginInvoke((a)=>reset.Set(), null); 
     reset.WaitOne(); 
    } 

Eso es todo, puede que sea un poco más compleja mediante la adición de otra función delegada a ejecutar después de la finalización o algo por el estilo ..

disfrutar!

+0

Si no me equivoco, supongo que ya tiene una función síncrona, ¿no? Cuando llamo a 'someobject.PerformAsyncAction();' pone en cola la acción y regresa de inmediato, no veo si transfiero mi función a su método que sabría cuando la acción realmente se realiza. – Davy8

+0

Cuando la acción finaliza, llama a la devolución de llamada que 'establece' el evento ManualReset. La función principal no volverá hasta que el ManualResetEvent esté 'configurado'. – Foole

+0

¿Cómo sabe cuándo se completa la acción? Todo lo que hace la función Acción es enviar un mensaje y luego regresa inmediatamente. – Davy8

Cuestiones relacionadas