8

我在 CodeProject 上找到了以下代码片段,用于异步调用方法... http://www.codeproject.com/Articles/14931/Asynchronous-Method-Invocation

private void CallFooWithOutAndRefParameters()
{
    // create the paramets to pass to the function
    string strParam1 = "Param1";
    int intValue = 100;
    ArrayList list = new ArrayList();
    list.Add("Item1");

    // create the delegate
    DelegateWithOutAndRefParameters delFoo =
      new DelegateWithOutAndRefParameters(FooWithOutAndRefParameters);

    // call the beginInvoke function!
    IAsyncResult tag =
        delFoo.BeginInvoke(strParam1,
            out intValue,
            ref list,
            null, null);

    // normally control is returned right away,
    // so you can do other work here...

    // calling end invoke notice that intValue and list are passed
    // as arguments because they might be updated within the function.
    string strResult =
        delFoo.EndInvoke(out intValue, ref list, tag);

    // write down the parameters:
    Trace.WriteLine("param1: " + strParam1);
    Trace.WriteLine("param2: " + intValue);
    Trace.WriteLine("ArrayList count: " + list.Count);
    Trace.WriteLine("return value: " + strResult);
}

关于这段代码,我有几件事不明白。

每个注释控件在到达 BeginInvoke 行时立即返回给调用代码。

这是否意味着后面的代码(EndInvoke 后跟一些跟踪日志记录)仅在 FooWithOutAndRefParameters 调用完成后运行......自动(即使该代码驻留在同一方法中)。我看起来有点困惑。(我一直对这种事情使用回调。)

使用此方法我必须调用 EndInvoke。我可以异步调用该方法并忘记它的发生吗?这有什么缺点吗?

如果我不调用 EndInvoke(如本方法所示),我应该总是有一个回调吗?即使回调什么也不做。

如果答案是您应该...那么您是调用 EndInvoke 还是定义一个回调?(定义回调的好处是通知您结果)

顺便说一句,我知道我可以在 EndInvoke 或回调中检查错误或记录结果(实际上我可能会这样做)。我想知道的是是否存在不调用 EndInvoke 或定义回调(例如内存泄漏)的风险?什么是最佳实践。

赛斯

4

3 回答 3

15

是的,您必须调用 EndInvoke()。不这样做会导致持续 10 分钟的相当严重的资源泄漏。底层管道是 .NET Remoting,10 分钟是“默认生命周期租用时间”。经常这样做,你的程序就会崩溃。让委托目标花费超过 10 分钟也有有趣的结果。

但最重要的是,如果不是调用操作的结果,则需要查看调用的方法是否成功完成。如果它死于异常,那么在调用 EndInvoke() 之前你不会发现它。此时会重新引发异常。实际上处理这个异常是相当棘手的,因为你真的不知道你的程序状态有多少在它爆炸之前被委托目标改变了。如果你真的想抓住它,那么目标没有太多的副作用是非常重要的。或者相反,目标捕获并重新抛出异常,并根据需要执行状态恢复。

您使用的示例当然是一个非常愚蠢的示例,很难在一种方法中在 BeginInvoke 和 EndInvoke 调用之间做任何有用的事情。您始终依赖于可以注册的回调,即 BeginInvoke 调用中倒数第二个参数。或者换句话说,不要null像示例代码那样通过。并且喜欢像 BackgroundWorker 和 Task 这样的类,甚至是 ThreadPool.QueueUserWorkItem() 来消除这段代码的刺痛。使用委托的 BeginInvoke() 方法是非常低级的黑客攻击。

委托是 .NET 中非常强大的抽象,但它们并没有很好地分配其功能。使用它们来实现事件是样板和无故障的。正确使用它的 BeginInvoke() 方法是一种黑带艺术。一个值得注意的细节是它不再在 .NETCore 中工作,对远程处理的支持已从 CoreCLR 中删除。

于 2012-07-23T21:22:14.070 回答
5

在任何事情之前,这些链接可能是有趣的:

异步调用同步方法

Delegate.EndInvoke() 真的有必要吗?


现在,关于您的问题:

每个注释控件在到达 BeginInvoke 行时立即返回给调用代码。

是的,调用是异步进行的(我可能假设它正在使用另一个线程)。

这是否意味着后面的代码(EndInvoke 后跟一些跟踪日志记录)仅在 FooWithOutAndRefParameters 调用完成后运行......自动(即使该代码驻留在同一方法中)。我看起来有点困惑。(我一直对这种事情使用回调。)

EndInvoke将阻塞执行,直到 BeginInvoke 启动的线程(方法)完成。在这种情况下,它类似于线程连接。

使用此方法我必须调用 EndInvoke。我可以异步调用该方法并忘记它的发生吗?这有什么缺点吗?

你必须总是打电话EndInvoke(见下文)。这可能有很多原因,但我认为最重要的一个是如果方法失败,通过抛出异常,在调用 EndInvoke 之前您不会收到异常。

如果我不调用 EndInvoke(如本方法所示),我应该总是有一个回调吗?即使回调什么也不做。

在这种情况下,回调必须EndInvoke从回调中调用。所以只有回调是可选的。

如果您应该回答...那么您是调用 EndInvoke 还是定义一个回调?(定义回调的好处是通知您结果)

您不必定义回调,但如果您这样做,则在其中调用 EndInvoke。

由您决定哪种方案更好:完全异步地通知方法完成或强制与方法连接(从而阻塞调用线程)。这都是关于控制的,你应该做一个或另一个,甚至两者兼而有之。

顺便说一句,我知道我可以在 EndInvoke 或回调中检查错误或记录结果(实际上我可能会这样做)。我想知道的是是否存在不调用 EndInvoke 或定义回调(例如内存泄漏)的风险?什么是最佳实践。

不是天生的,不,我不相信有风险。但是您必须始终检查该方法是否失败或成功完成。


MSDN中,以下是 BeginInvoke 之后可以执行的操作的选项:

  • 做一些工作,然后调用 EndInvoke 进行阻塞,直到调用完成。

  • 使用 IAsyncResult.AsyncWaitHandle 属性获取 WaitHandle,使用其 WaitOne 方法阻止执行,直到发出 WaitHandle 信号,然后调用 EndInvoke。

  • 轮询 BeginInvoke 返回的 IAsyncResult 以确定异步调用何时完成,然后调用 EndInvoke。

  • 将回调方法的委托传递给 BeginInvoke。当异步调用完成时,该方法在 ThreadPool 线程上执行。回调方法调用 EndInvoke。


OBS:正如@ScottChamberlain 在评论中所说,MSDN 指出

如果需要,您可以调用EndInvoke以从委托中检索返回值,但这不是必需的。EndInvoke 将阻塞,直到可以检索到返回值。

我认为其背后的原因是,在处理控件时,您是在 UI 线程上进行操作。由于 EndInvoke 阻塞了线程,您可能有理由不想这样做。尽管如此,我还是建议使用回调或轮询完成,以确保您的方法已成功完成。这将使您的程序更加健壮(或容错)。

于 2012-07-23T21:03:22.567 回答
1

作为实践,您应该调用 EndInvoke,因为调用者可能有处理程序订阅的事件可能对您的处理无关紧要,但对消费者可能很重要,以确保某些类型的处理可以在已知时间发生/应用状态。

于 2012-07-23T20:54:04.737 回答