繁体   English   中英

如何委派Control.BeginInvoke的AsyncCallback方法? (。净)

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

是否可以以“发射后忘记”的方式使用Control.BeginInvoke? 我想更改以下请求以委派回调方法,以便在每个异步调用完成后可以执行某些操作。

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

我可以用一个普通的委托来做到这一点。

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

但是因为我正在调用Control .BeginInvoke,所以无法执行此操作,因为出现了“跨线程操作无效”错误。
有人帮忙吗?

除了收到的一些答案之外,我还将澄清“为什么”。 我需要在我的GUI上加载/刷新控件而不锁定应用程序的其余部分。 该控件包含许多控件(ruleListCtls),所有这些都需要检索数据集并将其传递给它们。

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

我发现如果提供委托回调方法并将将对控件进行了修改的任何代码移回到创建它们的主GUI线程上,就可以做到这一点(避免跨线程错误)

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

这是我们委托给收到回调的主线程的内容:

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

现在可以正常工作了。 但是,除了我认为这不是特别优雅之外,主要问题是异常处理。 也许这是另一个问题,但是如果RefreshRulesDs抛出异常,那么我的应用程序将崩溃,因为该错误不是冒泡地返回到GUI线程(显然),而是未处理的异常。 直到我能抓住这些,然后我才必须同步完成整个操作。 如何成功捕获错误并加载其余控件? 或者如何通过适当的异常处理以另一种方式实现此异步操作?

关于“是否可能”部分:否, Control.BeginInvoke使用Windows的PostMessage() ,这意味着没有答案。 这也意味着RefreshRulesDelegate是在主线程上执行的,而不是在后台线程上执行的。

因此,请使用delegate.BeginInvoke或ThreadPool,并在完成后使用Control.[Begin]Invoke()更新UI。

您可以这样做:

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

编辑:

我会考虑从方法RefreshCompleted中删除IAsyncResult参数,并使用上述解决方案。

如果由于某种原因您确实需要保留IAsyncResult参数。 您可以实现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();
}

您将需要定义CustomAsyncResult类,您可以在此处获取有关如何执行此操作的文档

因此,您希望“额外的事情”发生在工作线程上吗? (否则,您只需在RefreshRules方法中运行它即可)。 也许只是使用ThreadPool.QueueUserItem

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

在您的RefreshRules方法的末尾(或之后)?

有关信息,您可能还会发现使用匿名方法调用BeginInvoke更加容易/整洁:

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

这避免了创建委托类型,并提供了对RefreshRules的调用的类型检查-请注意,它捕获ctrl ,因此,如果您处于循环中,则需要一个副本:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM