【问题标题】:Using BeginInvoke/EndInvoke in a multithreaded fashion. How do AsyncCallback, AsyncWaitHandle and IsCompleted interact?以多线程方式使用 BeginInvoke/EndInvoke。 AsyncCallback、AsyncWaitHandle 和 IsCompleted 如何交互?
【发布时间】:2009-01-02 14:30:39
【问题描述】:

Andreas Huber 对this question 的回答让我想到了使用异步委托而不是线程池来实现Concurrent<T>。但是,当AsyncCallback 传递给BeginInvoke 时,我发现很难理解发生了什么,尤其是当多个线程可以访问IAsyncResult 时。不幸的是,MSDN 或我能找到的任何地方似乎都没有涵盖这种情况。此外,我能找到的所有文章要么是在闭包和泛型可用之前编写的,要么看起来就是这样。有几个问题(我希望答案是真的,但我准备好失望了):

1) 使用闭包作为 AsyncCallback 会有什么不同吗?
(希望不是)
2)如果一个线程在AsyncWaitHandle上等待,是否会被通知
a) 在回调开始之前或 b) 完成后?
(希望是 b)
3) 当回调运行时,IsCompleted 会返回什么?我可以看到的可能性:
一)true; b)false; c) false 在回调调用 EndInvoke 之前,true 之后。
(希望是 b 或 c)
4) 如果在调用EndInvoke 之后某个线程在AsyncWaitHandle 上等待,会抛出DisposedObjectException 吗?
(希望不是,但我希望是的)。

如果答案如我所愿,这似乎应该可行:

public class Concurrent<T> { 
    private IAsyncResult _asyncResult;
    private T _result;

    public Concurrent(Func<T> f) { // Assume f doesn't throw exceptions
        _asyncResult = f.BeginInvoke(
                           asyncResult => {
                               // Assume assignment of T is atomic
                               _result = f.EndInvoke(asyncResult); 
                           }, null);
    }

    public T Result {
        get {
            if (!_asyncResult.IsCompleted)
                // Is there a race condition here?
                _asyncResult.AsyncWaitHandle.WaitOne();
            return _result;  // Assume reading of T is atomic
        }
    ...

如果问题 1-3 的答案是我所希望的,那么据我所知,这里应该没有种族条件。

【问题讨论】:

    标签: c# concurrency asynchronous delegates iasyncresult


    【解决方案1】:

    问题 1

    我认为部分问题在于误解。 IAsyncResult 不会从多个线程访问,除非您将其显式传递给一个。如果您查看 BCL 中 mos Begin*** 样式 API 的实现,您会注意到 IAsyncResult 仅在实际发生 Begin*** 或 End*** 调用的线程中创建和销毁。

    问题 2

    应该在操作 100% 完成后发出 AsyncWaitHandle 信号。

    问题 3

    一旦底层操作完成(无需再做任何工作),IsCompleted 应该返回 true。查看 IsComplete 的最佳方法是,如果该值为

    1. true -> 调用 End*** 将立即返回
    2. false -> Callind End*** 会阻塞一段时间

    问题 4

    这取决于实现。这里没有办法真正给出一个全面的答案。

    样本

    如果您对 API 感兴趣,它可以让您轻松地在另一个线程上运行委托并在完成后访问结果,请查看我的 RantPack Utility Library。它以源代码和二进制形式提供。它有一个完全充实的 Future API,允许委托的并发运行。

    此外,还有一个 IAsyncResult 的实现,它涵盖了这篇文章中的大部分问题。

    【讨论】:

    • 如果我想在这里实现 IAsyncResult,您的答案将非常适用,但我不这样做。我在询问 System.Runtime.Remoting.Messaging.AsyncResult 的行为,它实际上是由 BeginInvoke 返回的。
    • 问题 1:这正是我所说的“不幸的是,这个案例似乎在 MSDN 或任何我能找到的地方都没有涉及。”我没有直接公开 _asyncResult,而只是通过在 Result 属性中等待;但框架中的实现甚至不这样做。
    • 问题2:很好,这就是我所希望的。
    • 问题 3:如果 IsCompleted 为真并且已经调用了 EndInvoke 怎么办?再次调用 EndInvoke 安全吗?
    • 问题 4:我明白了,这就是我问具体实现的原因。
    【解决方案2】:

    我最近一直在研究异步调用。我找到了一个指向受人尊敬的作者 Jeffrey Richter 的 example implementation of an IAsyncResult 文章的指针。通过研究这个实现,我学到了很多关于异步调用如何工作的知识。

    您还可以查看是否可以下载并检查您特别关注的System.Runtime.Remoting.Messaging.AsyncResult 的源代码。这是link to instructions on how to do this in Visual Studio

    为 JaredPar 的好答案添加一点...

    1:我相信,如果您定义一个可以分配给 AsyncCallback 类型的变量的闭包(接受 IAsyncResult 并返回 void),它应该可以像您期望闭包作为该委托一样工作,但我不是确定是否可能存在范围问题。原始本地范围应该在回调被调用之前很久就返回了(这就是使它成为异步操作的原因),因此请记住对本地(堆栈)变量的引用及其行为方式。我认为引用成员变量应该没问题。

    2:我认为从您的评论中您可能误解了这个问题的答案。在 Jeffrey Richter 的示例实现中,等待句柄在调用回调之前发出信号。如果你考虑一下,它必须是这样的。一旦它调用回调,它就会失去对执行的控制。假设回调方法抛出一个异常......执行可以展开回调用回调的方法,从而阻止它以后发出等待句柄的信号!所以等待句柄需要在调用回调之前发出信号。与仅在回调返回后才向等待句柄发出信号相比,按该顺序完成它们的时间也更接近。

    3:正如 JaredPar 所说,IsCompleted 应该在 回调之前和等待句柄发出信号之前返回 true。这是有道理的,因为如果 IsCompleted 为 false,您会期望对 EndInvoke 的调用会阻塞,而等待句柄(与回调一样)的全部意义在于知道结果何时准备好并且它不会 块。因此,首先将 IsCompleted 设置为true,然后发出等待句柄信号,然后调用回调。看看 Jeffrey Richter 的例子是如何做到的。 然而,您可能应该尽量避免假设这三种方法(轮询、等待句柄、回调)可能检测到完成的顺序,因为可能以与预期不同的顺序实现它们。

    4:我帮不了你,除非你可以通过调试你感兴趣的实现的框架源代码来找到答案。或者你可能想出一个实验来找出答案......或者设置一个好的实验并调试到框架源代码中以确保真正确定。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多