在C# .Net 3.5中,从EventHandler.BeginInvoke回调中是否需要调用EndInvoke?

3
我正在建立一个需要可靠和可扩展的 WCF 客户端和服务器场景,并且作为其中的一部分,我正在尝试使其多线程化。诚然,我没有广泛的多线程经验。
在客户端上,来自服务器的回调(在此例中为 "OnMessage")触发了应该在后台传递的客户端事件。我将客户端的 CallbackBehavior ConcurrencyMode 设置为 Single(至少现在是这样)。因此,为了回复回调,而不是像正常情况下调用处理程序(无论是 handler(sender, eventargs 还是 handler.Invoke...),我正在调用 handler.BeginInvoke。
一切都很好,但在回调中,我想知道是否需要显式调用 EndInvoke,或者我可以什么也不做(这里显示出了我的缺乏多线程经验)。
public void OnMessage(Message message)
{
    EventHandler<MessageEventArgs> handler = OnServerMessage;
    if (handler != null)
        handler.BeginInvoke(this, new MessageEventArgs(message), CompleteHandler, handler);
}

public void CompleteHandler(IAsyncResult result)
{
    ((EventHandler<MessageEventArgs>)result.AsyncState).EndInvoke(result);
}

我能否用handler.EndInvoke的直接引用、一个空方法或者其他方式来替代CompleteHandler?是否有更好的方法?

此外,目前事件订阅方在等待控制台用户输入时会阻塞。当应用程序完成后,它将要么阻塞等待数据库,要么是自下而上的异步调用(因此可能不必要,但我仍然想知道)。

3个回答

8

是的,你必须这样做。这是唯一的方法来检查调用的方法是否抛出了异常。并清理远程调用的状态,以便它可以被垃圾回收,而不是让它再闲置10分钟。


谢谢提供信息。这是我发现自己不知道多少的情况之一。我需要做什么来“清理远程状态”?如果您可以建议任何文章/文档/示例供我阅读和学习,我将不胜感激。再次感谢。 - user358089
3
清理远程调用状态,只需调用 EndInvoke 即可 :) - Anton Tykhyy
你的回答意味着它会在一段时间内持续存在,然后才有资格进行垃圾回收。你确定吗? - sisve
@Simon - 很棒的新问题主题! - Hans Passant
根据微软公司的说法,您不必调用EndInvoke。引用:“如果需要,您可以调用EndInvoke来检索委托的返回值,但这不是必需的。EndInvoke将阻塞直到可以检索返回值。”来源:https://msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.90).aspx - Abacus
那与委托的BeginInvoke方法毫无关系。Control类不是委托类型。请阅读此内容:https://dev59.com/iWUp5IYBdhLWcg3w5KvV#15013968。 - Hans Passant

1

你需要将你的CompleteHandler代码放在try/catch块中,并正确处理EndInvoke抛出的任何异常。目前该服务在“实验室”条件下工作,但当出现问题时,你甚至不会知道发生了什么错误,因为你没有正确地处理/记录错误。

至于从全局状态、IASyncResult或回调闭包获取状态的问题:IASyncResult.AsyncState是最通用的解决方案,在其他人对调用代码进行重构后仍将正确地工作。


关于您的第二段话:抱歉,我不完全理解。您是指我如何访问处理程序以调用EndInvoke吗?如果是这样,那么您是想建议将其作为状态对象传递并在回调中从IAsyncResult.AsyncState检索它以调用EndInvoke的方法是否正确或不正确?谢谢。 - user358089
@Chris:我想我误解了问题。我以为你是在问是否有另一种方法在CompleteHandler内部获取对handler的引用。 - Remus Rusanu

0
一般来说,BeginInvoke 开始一个异步操作,而 EndInvoke 等待它完成。因此,这取决于你的意图:如果你想保证你的异步代码已经完成,你需要使用 EndInvoke,否则不需要。
(但是要注意,如果你的 BeginInvoke 调用到了你实际所在的同一线程(这是有效的用法,但代码将在下一个 Dispatcher 循环中运行),你不能使用 EndInvoke,因为这会创建死锁。但这似乎不是你的情况。)

谢谢。然而,我不知道在第一时间调用相同线程涉及到什么,因此我也不知道如何避免/控制它在将来发生。你有一个例子说明它与我当前的实现有何不同吗? - user358089

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接