如何为 Control.BeginInvoke 委托 AsyncCallback 方法? (。网)
是否可以以“即发即忘”方式以外的方式使用 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;
}
}
}
现在工作正常。然而,除了我认为这不是特别优雅之外,主要问题是异常处理。也许这是另一个问题,但如果 RefreshRulesDs 抛出异常,那么我的应用程序就会崩溃,因为该错误不会在 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
关于“是否可能”部分:不,
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 useControl.[Begin]Invoke()
to update the UI.您可以这样做:
编辑:
我会考虑从 RefreshCompleted 方法中删除 IAsyncResult 参数并使用上面的解决方案。
如果由于某种原因您确实需要保留 IAsyncResult 参数。您可以为 Control 实现扩展方法:
您需要定义 CustomAsyncResult 类,您可以获取有关如何执行此操作的文档 此处
You could do this:
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:
You would need to define your CustomAsyncResult class, you can get documentation on how to do this here
那么您希望“额外的事情”发生在工作线程上吗? (否则您只需在
RefreshRules
方法中运行它)。也许只是在
RefreshRules
方法的末尾(或之后)使用ThreadPool.QueueUserItem
: ?有关信息,您可能会发现使用匿名方法调用
BeginInvoke
也更容易/更整洁:这可以避免创建委托类型,并在调用
RefreshRules
时提供类型检查- 但请注意,它会捕获ctrl
- 因此,如果您处于循环中,则需要一个副本:So you want the "extra thing" to happen on a worker thread? (else you'd just run it in th
RefreshRules
method). Perhaps just useThreadPool.QueueUserItem
: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 avoids creating a delegate type, and provides type-checking on your call to
RefreshRules
- note that it capturesctrl
, though - so if you are in a loop you'll need a copy: