2009-11-27 4 views
5

Ist es möglich, Control.BeginInvoke in einem anderen als einem "Feuer & vergessen" Weise zu verwenden? Ich möchte die folgende Anfrage ändern, um eine Rückrufmethode zu delegieren, so dass ich etwas tun kann, wenn jeder meiner asynchronen Anrufe abgeschlossen ist.Wie delegiere ich eine AsyncCallback-Methode für Control.BeginInvoke? (.NET)

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

Ich wäre in der Lage, dies mit einem normalen delegate.BeginInvoke z.

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

Aber weil ich Kontrolle .BeginInvoke bin Aufruf Ich kann das nicht tun, wie ich den „Cross-Thread-Betrieb ungültig“ Fehler.
Wer hilft?

Weiter zu einigen der Antworten erhalten, werde ich das "warum" klären. Ich muss ein Control auf meiner GUI laden/aktualisieren, ohne den Rest der App zu sperren. Das Steuerelement enthält zahlreiche Steuerelemente (ruleListCtls), die erfordern, dass ein Dataset abgerufen und an sie übergeben wird. dh

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

Ich habe festgestellt, daß ich dies tun kann, wenn ich einen Delegierten Callback-Methode zur Verfügung stellen und einen Code bewegen, die die Kontrollen zurück auf die Haupt GUI-Thread ändert, auf dem sie erzeugt wurden (die Querfadenfehler zu vermeiden,)

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

Hier ist, was wir auf der Haupt-Thread delgate den Rückruf erhalten zu haben:

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

funktioniert gut Dieses jetzt. Das Hauptproblem, abgesehen davon, dass das nicht besonders elegant ist, ist das Ausnahme-Handling. Vielleicht ist das eine andere Frage, aber wenn RefreshRulesDs eine Ausnahme auslöst, stürzt meine App ab, da der Fehler nicht in den GUI-Thread zurückgeblasen wird (offensichtlich), sondern als unbehandelte Ausnahme. Bis ich diese fangen kann, muss ich diese ganze Operation synchron machen. Wie kann ich einen Fehler erfolgreich erfassen und den Rest meiner Steuerelemente laden? Oder wie erreiche ich diese asynchrone Operation auf eine andere Art und Weise, mit der richtigen Ausnahmebehandlung?

+0

Die Ausnahmebehandlung ist ein bisschen unordentlich, aber Sie sollten in der Lage sein, sie durch umgebende EndInvoke zu fangen. BackgroundWorker und Task (Fx 4) machen es aber netter. –

Antwort

4

zu tun, den Bezug auf „Ist es möglich,“ Teil: Nein, Control.BeginInvoke verwendet Windows 'PostMessage() und das bedeutet, dass es keine Antwort gibt. Dies bedeutet auch, dass RefreshRulesDelegate im Hauptthread ausgeführt wird, nicht in einem Hintergrundthread.

Verwenden Sie also delegate.BeginInvoke oder den ThreadPool und verwenden Sie Control.[Begin]Invoke(), um die Benutzeroberfläche zu aktualisieren.

+0

Henk genannt werden, Ihre Hilfe wird geschätzt. Ich habe meinen Code wie vorgeschlagen geändert. Siehe oben. Die (Millionen-Dollar-) Frage lautet nun: Wie gehe ich mit irgendwelchen Ausnahmen um? – Sheed

0

Sie wollen also die "Extra-Sache" auf einem Arbeiter-Thread passieren? (sonst würden Sie es einfach in der Methode RefreshRules ausführen). Vielleicht verwenden Sie einfach ThreadPool.QueueUserItem:

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

am Ende (oder nach) Ihre RefreshRules Methode?

Für Informationen, können Sie finden es einfacher/aufgeräumter zu BeginInvoke mit einer anonymen Methode aufzurufen:

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

dies vermeidet einen Delegaten Art zu schaffen, und bietet typ Überprüfung auf Ihren Anruf RefreshRules - beachten Sie, dass es fängt ctrl, obwohl - wenn Sie also in einer Schleife sind finden Sie eine Kopie benötigen:

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

Danke. Nicht sicher, dass das hilft. Ich möchte nur, dass RefreshCompleted aufgerufen wird, wenn RefreshRules beendet ist. Ich dachte, das wäre einfach – Sheed

+0

es wäre einfacher zu erklären, wenn Sie ein wenig die Ursache der Frage erklären. Welches Problem hat deine Frage verursacht? Vielleicht gibt es einen einfacheren Weg, es zu tun. – serhio

2

Sie können dies tun:

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

EDIT:

halte ich würde das IAsyncResult Argument von der Methode RefreshCompleted entfernen und die Lösung oben verwenden.

Wenn Sie aus irgendeinem Grund das IAsyncResult-Argument wirklich beibehalten müssen. Sie könnten eine Erweiterungsmethode für Steuerung implementieren:

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

Sie müßten Ihre CustomAsyncResult Klasse definieren, können Sie die Dokumentation bekommen, wie diese here

+0

Danke. RefreshCompleted hat den IAsyncResult-Parameter, auf den ich zu diesem Zeitpunkt keinen Zugriff habe. Auch möchte ich RefreshCompleted als AsyncCallback – Sheed

Verwandte Themen