Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I delegate an AsyncCallback method for Control.BeginInvoke? (.NET)

Is it possible to use Control.BeginInvoke in anything other than a "fire & forget" manner? I want to change the following request to delegate a callback method so that i can do something when each of my asynchronous calls complete.

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

I would be able to do this with a normal delegate.BeginInvoke e.g.

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

But because I'm calling Control.BeginInvoke I can't do this as I get the "cross-thread operation not valid" error.
Anyone help?

Further to some of the answers received, I will clarify the "why". I need to load/refresh a Control on my GUI without locking up the rest of the app. The control contains numerous controls (ruleListCtls) which all require a dataset to be retrieved and passed to them. i.e.

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

I have found that I can do this if I provide a delegate callback method and move any code which amends the controls back onto the main GUI thread on which they were created (to avoid the cross-thread error)

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

Here's what we delgate to the main thread having received the callback:

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

This now works fine. However, the main problem, apart from that I don't think this is particularly elegant, is exception handling. Maybe this is another question, but if RefreshRulesDs throws an exception then my app crashes as the error is not bubbled back up the GUI thread (obviously) but as an unhandled exception. Until I can catch these then I will have to do this whole operation synchronously. How do I successfully catch an error and load up the rest of my controls? Or how do I do achieve this asynchronous operation another way, with proper exception handling?

like image 343
Sheed Avatar asked Nov 27 '09 12:11

Sheed


1 Answers

Regarding the "Is it possible" part: No, Control.BeginInvoke uses Windows' PostMessage() and that means there is no answer. It also means that the RefreshRulesDelegate is executed on the main thread, not on a background thread.

So, use delegate.BeginInvoke or the ThreadPool and when they are completed use Control.[Begin]Invoke() to update the UI.

like image 151
Henk Holterman Avatar answered Sep 27 '22 18:09

Henk Holterman