【问题标题】:How do I delegate an AsyncCallback method for Control.BeginInvoke? (.NET)如何为 Control.BeginInvoke 委托 AsyncCallback 方法? (。网)
【发布时间】:2010-12-20 23:40:51
【问题描述】:

是否可以以“即发即弃”方式以外的方式使用 Control.BeginInvoke? 我想更改以下请求以委托回调方法,以便在我的每个异步调用完成时我可以做一些事情。

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

我可以使用普通的 delegate.BeginInvoke 来做到这一点,例如

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 线程中冒泡(显然),而是作为未处理的异常。在我能捕捉到这些之前,我将不得不同步完成整个操作。如何成功捕获错误并加载其余控件?或者如何通过适当的异常处理以另一种方式实现此异步操作?

【问题讨论】:

  • 异常处理有点乱,但是你应该可以通过包围 EndInvoke 来捕获它们。 BackgroundWorker 和 Task (Fx 4) 做得更好。

标签: c# .net begininvoke asynccallback


【解决方案1】:

所以您希望“额外的事情”发生在工作线程上? (否则你只需在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 */ });
});

【讨论】:

  • 谢谢。不确定这是否有帮助。我只想在 RefreshRules 完成时调用 RefreshCompleted。我认为这很简单
  • 如果你稍微解释一下问题的原因,它会更容易解释。什么问题导致了你的问题。也许有更简单的方法。
【解决方案2】:

你可以这样做:

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 类,您可以获取有关如何执行此操作的文档here

【讨论】:

  • 谢谢。 RefreshCompleted 具有 IAsyncResult 参数,但我当时无权访问该参数。我也希望 RefreshCompleted 被称为 AsyncCallback
【解决方案3】:

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

所以,使用delegate.BeginInvoke 或线程池,完成后使用Control.[Begin]Invoke() 更新用户界面。

【讨论】:

  • Henk,感谢您的帮助。我已经按照您的建议修改了我的代码。往上看。现在(百万美元)的问题是我如何处理抛出的任何异常?
猜你喜欢
  • 2011-09-10
  • 2012-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-13
  • 2016-11-10
相关资源
最近更新 更多