2009-11-27 7 views
5

¿Es posible usar Control.BeginInvoke en algo que no sea "fire & forget"? Deseo cambiar la siguiente solicitud para delegar un método de devolución de llamada para que pueda hacer algo cuando se complete cada una de mis llamadas asincrónicas.¿Cómo delego un método AsyncCallback para Control.BeginInvoke? (.NET)

this.BeginInvoke(new RefreshRulesDelegate(RefreshRules), new object[] { ctrl, ctrl.DsRules, ctrl.CptyId }); 

que sería capaz de hacer esto con una normal de delegate.BeginInvoke por ejemplo

RefreshRulesDelegate del = new RefreshRulesDelegate(RefreshRules); 
      del.BeginInvoke(ctrl, ctrl.DsRules, ctrl.CptyId, new AsyncCallback(RefreshCompleted), del); 

Pero debido a que estoy llamando control .BeginInvoke No puedo hacer esto como llegue a la "operación entre subprocesos no válida" error.
¿Alguien ayuda?

Además de algunas de las respuestas recibidas, aclararé el "por qué". Necesito cargar/actualizar un Control en mi GUI sin bloquear el resto de la aplicación. El control contiene numerosos controles (ruleListCtls) que requieren un conjunto de datos para ser recuperados y pasados ​​a ellos. es decir

public void RefreshAll() 
{ 
    foreach (LTRFundingRuleListControl ctrl in ruleListCtls) 
    { 
     this.BeginInvoke(new RefreshRulesDelegate(RefreshRules), new object[]{ctrl,ctrl.DsRules, ctrl.CptyId }); 
    } 
} 

he encontrado que puedo hacer esto si proporcionar un método delegado de devolución de llamada y mover cualquier código que modifica los controles de nuevo en el hilo GUI principal en el que se crearon (para evitar el error de hilo cruzado)

public void RefreshAll() 
{ 
    IntPtr handle; 
    foreach (LTRFundingRuleListControl ctrl in ruleListCtls) 
    { 
     handle = ctrl.Handle; 
     RefreshRulesDsDelegate del = new RefreshRulesDsDelegate(RefreshRulesDs); 
     del.BeginInvoke(ctrl.DsRules, ctrl.CptyId, handle, out handle, new AsyncCallback(RefreshCompleted), del); 
    }   
} 

private void RefreshCompleted(IAsyncResult result) 
{ 
    CptyCatRuleDataSet dsRules; 
    string cptyId; 
    IntPtr handle; 

    AsyncResult res = (AsyncResult) result; 

    // Get the handle of the control to update, and the dataset to update it with 
    RefreshRulesDsDelegate del = (RefreshRulesDsDelegate) res.AsyncDelegate; 
    dsRules = del.EndInvoke(out handle,res); 

    // Update the control on the thread it was created on 
    this.BeginInvoke(new UpdateControlDatasetDelegate(UpdateControlDataset), new object[] {dsRules, handle}); 
} 

public delegate CptyCatRuleDataSet RefreshRulesDsDelegate(CptyCatRuleDataSet dsRules, string cptyId, IntPtr ctrlId, out IntPtr handle); 
private CptyCatRuleDataSet RefreshRulesDs(CptyCatRuleDataSet dsRules, string ruleCptyId, IntPtr ctrlId, out IntPtr handle) 
{ 
    try 
    { 
     handle = ctrlId; 
     int catId = ((CptyCatRuleDataSet.PSLTR_RULE_CAT_CPTY_SelRow)dsRules.PSLTR_RULE_CAT_CPTY_Sel.Rows[0]).RULE_CAT_ID; 
      return ltrCptyRulesService.GetCptyRules(ruleCptyId, catId); 
    } 
    catch (Exception ex) 
    { 
     throw ex; 
    } 
} 

Aquí es lo que delgate al hilo principal de haber recibido la devolución de llamada:

private delegate void UpdateControlDatasetDelegate(CptyCatRuleDataSet dsRules, IntPtr ctrlId); 
private void UpdateControlDataset(CptyCatRuleDataSet dsRules, IntPtr ctrlId) 
{ 
    IEnumerator en = ruleListCtls.GetEnumerator(); 
    while (en.MoveNext()) 
    { 
     LTRFundingRuleListControl ctrl = en.Current as LTRFundingRuleListControl; 
     if (ctrl.Handle == ctrlId) 
     { 
      ctrl.DsRules = dsRules; 
     } 
    } 
} 

Esto ahora funciona bien. Sin embargo, el problema principal, aparte de eso, no creo que esto sea particularmente elegante, es el manejo de excepciones. Tal vez esta es otra pregunta, pero si RefreshRulesDs arroja una excepción, entonces mi aplicación falla ya que el error no se borra una copia de seguridad del hilo de la GUI (obviamente) sino como una excepción no controlada. Hasta que pueda atraparlos, tendré que hacer toda esta operación sincrónicamente. ¿Cómo atrapo exitosamente un error y cargo el resto de mis controles? ¿O cómo puedo lograr esta operación asíncrona de otra manera, con el manejo de excepciones adecuado?

+0

El manejo de excepciones es un poco desordenado, pero deberías poder atraparlos rodeando a EndInvoke. BackgroundWorker y Task (Fx 4) lo hacen mejor. –

Respuesta

4

En cuanto a la "¿Es posible" parte: No, Control.BeginInvoke usa Windows 'PostMessage() y eso significa que no hay respuesta. También significa que RefreshRulesDelegate se ejecuta en el hilo principal, no en un hilo de fondo.

Por lo tanto, use delegate.BeginInvoke o ThreadPool y cuando se completen, use Control.[Begin]Invoke() para actualizar la interfaz de usuario.

+0

Henk, se agradece su ayuda. He modificado mi código como sugirió. Véase más arriba. La pregunta (de un millón de dólares) ahora es ¿cómo manejo cualquier excepción lanzada? – Sheed

0

¿Desea que ocurra algo "extra" en un hilo de trabajo? (de lo contrario, lo ejecutaría en el método RefreshRules). Tal vez sólo tiene que utilizar ThreadPool.QueueUserItem:

ThreadPool.QueueUserWorkItem(delegate { /* your extra stuff */ }); 

al final (o después) de su método de RefreshRules?

Para información, puede que le resulte más fácil/más ordenado para llamar BeginInvoke un método anónimo también:

this.BeginInvoke((MethodInvoker) delegate { 
    RefreshRules(ctrl, ctrl.DsRules, ctrl.CptyId); 
    ThreadPool.QueueUserWorkItem(delegate { /* your extra stuff */ }); 
}); 

esto evita la creación de un tipo de delegado, y proporciona la comprobación de tipos en su llamada a RefreshRules - en cuenta que captura ctrl, aunque - por lo que si usted está en un bucle que necesitará una copia:

var tmp = ctrl; 
this.BeginInvoke((MethodInvoker) delegate { 
    RefreshRules(tmp, tmp.DsRules, tmp.CptyId); 
    ThreadPool.QueueUserWorkItem(delegate { /* your extra stuff */ }); 
}); 
+0

Gracias. Sin embargo, no estoy seguro de que eso ayude. Solo quiero que se llame a RefreshCompleted cuando RefreshRules haya terminado. Pensé que sería simple – Sheed

+0

sería más fácil de explicar, si explica un poco la causa de la pregunta. ¿Qué problema causó tu pregunta? Tal vez hay una manera más fácil de hacerlo. – serhio

2

usted puede hacer esto:

this.BeginInvoke(delegate 
{ 
    RefreshRules(ctrl, ctrl.DsRules, ctrl.CptyId); 
    RefreshCompleted(); 
}); 

EDIT:

yo consideraría retirar el argumento del método IAsyncResult RefreshCompleted y utilizar la solución anterior.

Si por alguna razón realmente necesita mantener el argumento IAsyncResult. Se podría aplicar un método de extensión para el Control:

public static IAsyncResult BeginInvoke(this Control control, Delegate del, object[] args, AsyncCallback callback, object state) 
{ 
    CustomAsyncResult asyncResult = new CustomAsyncResult(callback, state); 
    control.BeginInvoke(delegate 
    { 
     del.DynamicInvoke(args); 
     asyncResult.Complete(); 
    }, args); 

    return asyncResult; 
} 

public static void EndInvoke(this Control control, IAsyncResult asyncResult) 
{ 
    asyncResult.EndInvoke(); 
} 

Usted tendría que definir la clase CustomAsyncResult, puede obtener documentación sobre cómo hacer esto here

+0

Gracias. RefreshCompleted tiene el parámetro IAsyncResult aunque no tengo acceso en ese momento. También quiero que se llame a RefreshCompleted como AsyncCallback – Sheed

Cuestiones relacionadas