以多线程方式使用BeginInvoke / EndInvoke。 AsyncCallback,AsyncWaitHandle和IsCompleted如何交互?

时间:2009-01-02 14:30:39

标签: c# concurrency asynchronous delegates iasyncresult

Andreas Huber对this question的回答让我想到用异步委托而不是ThreadPool实现Concurrent<T>。但是,我发现将AsyncCallback传递给BeginInvoke时会发生什么变得更加困难,尤其是当多个线程可以访问IAsyncResult时。不幸的是,这个案例似乎没有在MSDN或我能找到的任何地方涵盖。此外,我能找到的所有文章都是在关闭之前编写的,而且可以使用泛型,或者只是这样。有几个问题(我希望答案是正确的,但我准备失望了):

1)使用闭包作为AsyncCallback会有什么不同吗?
(希望不是)
2)如果某个帖子在AsyncWaitHandle上等待,则会发出信号 a)在回调开始之前或 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的答案是我希望的答案,那么就我所知,这里应该没有任何比较条件。

2 个答案:

答案 0 :(得分:2)

问题1

我认为部分问题是误解。除非您明确地将IAsyncResult传递给多个线程,否则不会从多个线程访问IAsyncResult。如果你看一下BCL中mos Begin ***风格API的实现,你会发现IAsyncResult只是从实际发生Begin ***或End ***调用的线程中创建和销毁的。

问题2

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

问题3

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

  1. true - &gt; Calling End ***将立即返回
  2. false - &gt; Callind End ***将阻止一段时间
  3. 问题4

    这取决于实现。这里没有办法真正给出答案。

    <强>样品

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

    此外,还有IAsyncResult的实现,它涵盖了本文中的大部分问题。

答案 1 :(得分:2)

我最近一直在寻找异步电话。我找到了一个指向着名作家杰弗里里希特的example implementation of an IAsyncResult文章的指针。通过研究这种实现,我学到了很多关于异步调用的工作原理。

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

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

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

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

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

4:我无法帮助你,除了你可以通过调试框架源代码找到你想知道的实现的答案。或者你可能想出一个实验来找出...或者设置一个好的实验并调试到框架源来确定。