如何将一个AsyncCallback方法委托给Control.BeginInvoke?(. net)

时间:2022-08-27 20:46:45

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.

我可以用一个普通的委托来做这个。BeginInvoke如。

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?

但因为我在呼叫控制。BeginInvoke方法我不能这样做,因为我得到了“跨线程操作无效”错误。有人帮助吗?

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.

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

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)

我发现,如果我提供一个委托回调方法并将修改控件的任何代码移回创建控件的主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;
    }
}  

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

以下是我们对接收回调的主线程的delgate:

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?

现在工作好。然而,主要的问题是异常处理,除此之外,我认为这不是特别优雅。也许这是另一个问题,但是如果RefreshRulesDs抛出一个异常,那么我的应用程序崩溃了,因为错误并没有冒冒失失地备份到GUI线程(显然),而是作为一个未处理的异常。在捕捉到这些之前,我必须同步执行整个操作。如何成功地捕获错误并加载其余的控件?或者,我如何以另一种方式实现这个异步操作,以及正确的异常处理?

3 个解决方案

#1


4  

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.

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

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

所以,使用委托。BeginInvoke或ThreadPool,当它们完成时使用Control.[Begin]Invoke()更新UI。

#2


2  

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.

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

If for some reason you really need to keep the IAsyncResult argument. You could implement an extension method for Control:

如果由于某种原因,您确实需要保留IAsyncResult参数。您可以实现一种扩展方法进行控制:

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

您将需要定义CustomAsyncResult类,您可以在这里获得如何实现这一点的文档

#3


0  

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:

那么,您希望在工作线程上发生“额外的事情”吗?(否则你只能在RefreshRules方法中运行它)。也许只用ThreadPool.QueueUserItem:

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

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

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

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

对于info,您可能会发现,使用匿名方法调用BeginInvoke更容易/更容易:

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:

这样可以避免创建委托类型,并在调用RefreshRules时提供类型检查——注意它捕获了ctrl,所以如果您处于循环中,则需要一个副本:

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

#1


4  

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.

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

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

所以,使用委托。BeginInvoke或ThreadPool,当它们完成时使用Control.[Begin]Invoke()更新UI。

#2


2  

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.

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

If for some reason you really need to keep the IAsyncResult argument. You could implement an extension method for Control:

如果由于某种原因,您确实需要保留IAsyncResult参数。您可以实现一种扩展方法进行控制:

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

您将需要定义CustomAsyncResult类,您可以在这里获得如何实现这一点的文档

#3


0  

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:

那么,您希望在工作线程上发生“额外的事情”吗?(否则你只能在RefreshRules方法中运行它)。也许只用ThreadPool.QueueUserItem:

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

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

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

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

对于info,您可能会发现,使用匿名方法调用BeginInvoke更容易/更容易:

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:

这样可以避免创建委托类型,并在调用RefreshRules时提供类型检查——注意它捕获了ctrl,所以如果您处于循环中,则需要一个副本:

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