如何为 Control.BeginInvoke 委托 AsyncCallback 方法? (。网)

发布于 2024-08-13 10:48:01 字数 3157 浏览 4 评论 0原文

是否可以以“即发即忘”方式以外的方式使用 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 上加载/刷新控件,而不锁定应用程序的其余部分。该控件包含许多控件 (ruleListCtl),这些控件都需要检索数据集并将其传递给它们。即

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

现在工作正常。然而,除了我认为这不是特别优雅之外,主要问题是异常处理。也许这是另一个问题,但如果 R​​efreshRulesDs 抛出异常,那么我的应用程序就会崩溃,因为该错误不会在 GUI 线程中冒泡(显然),而是作为未处理的异常。在我能够捕获这些之前,我将不得不同步完成整个操作。如何成功捕获错误并加载其余控件?或者我如何以另一种方式实现这个异步操作,并进行适当的异常处理?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

眉目亦如画i 2024-08-20 10:48:01

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

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

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.

撧情箌佬 2024-08-20 10:48:01

您可以这样做:

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

You could do this:

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

EDIT:

I would consider removing the IAsyncResult argument from the method RefreshCompleted and use the solution above.

If for some reason you really need to keep the IAsyncResult argument. You could implement an extension method for 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();
}

You would need to define your CustomAsyncResult class, you can get documentation on how to do this here

碍人泪离人颜 2024-08-20 10:48:01

那么您希望“额外的事情”发生在工作线程上吗? (否则您只需在 RefreshRules 方法中运行它)。也许只是

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

RefreshRules 方法的末尾(或之后)使用 ThreadPool.QueueUserItem: ?

有关信息,您可能会发现使用匿名方法调用 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 */ });
});

So you want the "extra thing" to happen on a worker thread? (else you'd just run it in th RefreshRules method). Perhaps just use ThreadPool.QueueUserItem:

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

at the end of (or after) your RefreshRules method?

For info, you may find it easier/tidier to call BeginInvoke with an anonymous method too:

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

this avoids creating a delegate type, and provides type-checking on your call to RefreshRules - note that it captures ctrl, though - so if you are in a loop you'll need a copy:

var tmp = ctrl;
this.BeginInvoke((MethodInvoker) delegate {
    RefreshRules(tmp, tmp.DsRules, tmp.CptyId);
    ThreadPool.QueueUserWorkItem(delegate { /* your extra stuff */ });
});
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文