【问题标题】:How does Delegate.BeginInvoke signal the wait handle in the implementation of BeginRead in .NET 2.0?Delegate.BeginInvoke 如何在 .NET 2.0 中的 BeginRead 实现中向等待句柄发出信号?
【发布时间】:2016-06-02 14:47:49
【问题描述】:

下面是 System.IO.FileStream.BeginRead 方法在 .NET 2.0 中的实现。

如您所见,实现将操作传递给ReadDelegateBeginInvoke 方法。

但是,在这样做之前,它会初始化一个AutoResetEvent,然后在其上调用WaitOne

但是,我看不出ReadDelegate 是如何向AutoResetEvent 发出信号的,因为它不会引用它。

您能解释一下这是如何工作的吗?

[HostProtection(SecurityAction.LinkDemand, ExternalThreading=true)]
public virtual IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
    if (!this.CanRead)
    {
        __Error.ReadNotSupported();
    }
    Interlocked.Increment(ref this._asyncActiveCount);
    ReadDelegate delegate2 = new ReadDelegate(this.Read);
    if (this._asyncActiveEvent == null)
    {
        lock (this)
        {
            if (this._asyncActiveEvent == null)
            {
                this._asyncActiveEvent = new AutoResetEvent(true);
            }
        }
    }
    this._asyncActiveEvent.WaitOne();
    this._readDelegate = delegate2;
    return delegate2.BeginInvoke(buffer, offset, count, callback, state);
}

【问题讨论】:

  • AutoResetEvent 从一开始就在信号状态下创建(这就是true 的用途),因此如果事件刚刚创建,.WaitOne() 会立即返回。这是答案的一半——另一个是解释.WaitOne() 的用途,如果事件已经存在(确保一次只能读取一次?)
  • 这个源码是反编译的结果吗?
  • @YacoubMassad 是的。我在 Reflector 中看到过。
  • @JeroenMostert 让我感到困惑的另一件事是,MSDN 中的许多示例还将 AutoResetEvent 的初始状态设置为 true 并且仍然等待它然后发出信号,同时暗示wait 阻塞当前线程,直到事件被显式发出信号。我仍然对initialState 的用途感到困惑。你所说的直观是有道理的,但 MSDN 让我感到困惑。考虑这个例子。请参阅示例代码中的event_1msdn.microsoft.com/en-us/library/…
  • 听起来可能是第二个问题。 :-) 但是,一般来说,您应该在 MSDN 示例中放很少的库存——它们通常是微不足道的、令人困惑的、不正确的或三者的任意组合。这个特定的示例虽然没有错,但只是为了说明行为——它并不代表一个实际的用例。在任何情况下,在信号状态下创建的AutoResetEvent 在第一次调用.WaitOne() 时将不会 阻塞。它将阻止后续调用,直到调用 .Set()

标签: c# .net multithreading asynchronous


【解决方案1】:

反编译器无法帮助您检索原始源代码中的 cmets。从 SSCLI20 发行版中可用于 .NET 2.0。内容如下:

       // To avoid a race with a stream's position pointer & generating race
       // conditions with internal buffer indexes in our own streams that
       // don't natively support async IO operations when there are multiple
       // async requests outstanding, we will block the application's main
       // thread if it does a second IO request until the first one completes.
       if (_asyncActiveEvent == null) {
           lock(this) {
               if (_asyncActiveEvent == null)
                   _asyncActiveEvent = new AutoResetEvent(true);
           }
       }
       bool r = _asyncActiveEvent.WaitOne();
       BCLDebug.Assert(r, "AutoResetEvent didn't get a signal when we called WaitOne!");

       BCLDebug.Assert(_readDelegate == null && _writeDelegate == null, 
           "Expected no other readers or writers!");

因此,如果 _asyncActiveEvent 为 null,则不会有任何其他异步 I/O 操作在进行中,因此阻止该操作没有任何意义。因此,初始化要设置的 ARE 是非常有目的的。 WaitOne() 调用再次将其重置,因此在前一个完成之前第二次调用 BeginRead() 将阻塞并避免竞争条件。 EndRead() 通过重新设置解除阻塞。

可能的心理障碍是 ARE 从其正常用法向后使用。仅在无事发生时才设置。

【讨论】:

  • 哇!哇!仅仅因为你的精彩回答,我正在养成新的好习惯。我现在将从 referencesource.com 阅读当前版本的源代码,并通过从 SSCLI 分发站点下载其源代码来阅读以前版本的源代码。事情是:我想将它加载到我机器上的 Visual Studio 中,然后单步执行它并用它做一些肮脏的事情,但我很害怕。我不知道该怎么做。我看到一个 readmefirst.html,它说我需要 ActivePerl,一个老牧师和一个年轻牧师,然后我才能对此有信心。我害怕。我是傻还是怎么的?
  • 我只是感到挑战。我时不时地发现自己是多么的无能。
  • 而我刚刚意识到我仍然不明白,即使是我认为到目前为止我理解的一点点线程。我特别是。不明白AutoResetEvent 的行为方式。
  • 从您的回答来看,您似乎暗示AutoResetEvent(或任何其他等待句柄)的信号状态是中性的。您可以将其设置为true,这并不意味着任何事情。当一个线程发出状态信号时,它只是翻转状态位,从而产生新值false?我跟着你正确吗?另外,当你说WaitOne 重置它时,我很困惑。 WaitOne 不应该只是被动地等待它发出信号吗?
  • 尤里卡!我一直在重新阅读那段代码,我认为正确的解释已经浮出水面。第一个获取锁并初始化_asyncActiveEvent 的线程,AutoResetEvent 字段不会阻塞WaitOne,因为等待句柄已经发出信号,这意味着目前还没有其他线程尝试运行此方法。此后的任何其他并发线程都将找到一个已经初始化的等待句柄,并将阻塞WaitOne 调用。是啊。好像就是这样。理智已经恢复。我之前所知道的一切都与这个解释一致。
【解决方案2】:

似乎我在问题中提到的AutoResetEvent 不是用来表示ReadDelegate 的完成,因为它已经通过回调委托给委托上的BeginInvoke 方法,但要确保@ 987654324@ 被调用,从而将完整的BeginXXXEndXXX 操作作为一个单元保持原子性。

这可以从同一个类的EndRead方法的代码中推断出来。

public virtual int EndRead(IAsyncResult asyncResult)
{
    if (asyncResult == null)
    {
        throw new ArgumentNullException("asyncResult");
    }
    if (this._readDelegate == null)
    {
        throw new ArgumentException(Environment.GetResourceString("InvalidOperation_WrongAsyncResultOrEndReadCalledMultiple"));
    }
    int num = -1;
    try
    {
        num = this._readDelegate.EndInvoke(asyncResult);
    }
    finally
    {
        this._readDelegate = null;
        this._asyncActiveEvent.Set();
        this._CloseAsyncActiveEvent(Interlocked.Decrement(ref this._asyncActiveCount));
    }
    return num;
}

AutoResetEvent 由标识符_asyncActiveEvent 表示,这是一个类级别的实例变量。

【讨论】:

    猜你喜欢
    • 2016-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-30
    相关资源
    最近更新 更多