4

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

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

如果答案如我所愿,这似乎应该有效:

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 的答案是我所希望的,那么据我所知,这里应该没有种族条件。

4

2 回答 2

2

问题 1

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

问题2

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

问题 3

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

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

问题 4

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

样品

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

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

于 2009-01-02T14:46:45.567 回答
2

我最近一直在研究异步调用。我找到了一篇文章的指针,其中包含受人尊敬的作者 Jeffrey Richter的 IAsyncResult 示例实现。通过研究这个实现,我学到了很多关于异步调用如何工作的知识。

您还可以查看是否可以下载并检查System.Runtime.Remoting.Messaging.AsyncResult您特别关注的源代码。这是有关如何在 Visual Studio 中执行此操作的说明的链接

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

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

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

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

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

于 2009-09-02T08:31:35.883 回答