C# 如果操作首先完成,则使用RegisterWaitForSingleObject

C# 如果操作首先完成,则使用RegisterWaitForSingleObject,c#,.net,multithreading,asynchronous,C#,.net,Multithreading,Asynchronous,我正在使用Begin/End风格的方法进行一些异步网络I/O。(这实际上是对Azure表存储的查询,但我认为这并不重要。)我已经使用ThreadPool.RegisterWaitForSingleObject()实现了客户端超时。据我所知,这很好用 由于ThreadPool.RegisterWaitForSingleObject()将WaitHandle作为参数,因此我必须开始I/O操作,然后执行ThreadPool.RegisterWaitForSingleObject()。这似乎引入了一种可

我正在使用Begin/End风格的方法进行一些异步网络I/O。(这实际上是对Azure表存储的查询,但我认为这并不重要。)我已经使用
ThreadPool.RegisterWaitForSingleObject()实现了客户端超时。据我所知,这很好用

由于
ThreadPool.RegisterWaitForSingleObject()
WaitHandle
作为参数,因此我必须开始I/O操作,然后执行
ThreadPool.RegisterWaitForSingleObject()
。这似乎引入了一种可能性,即I/O在我注册等待之前就完成了

简化的代码示例:

private void RunQuery(QueryState queryState)
{
    //Start I/O operation
    IAsyncResult asyncResult = queryState.Query.BeginExecuteSegmented(NoopAsyncCallback, queryState);

    //What if the I/O operation completes here? 

    queryState.TimeoutWaitHandle = ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, QuerySegmentCompleted, asyncResult, queryTimeout, true);
}

private void QuerySegmentCompleted(object opState, bool timedOut){
    IAsyncResult asyncResult = opState as IAsyncResult;
    QueryState state = asyncResult.AsyncState as QueryState;

    //If the I/O completed quickly, could TimeoutWaitHandle could be null here?
    //If so, what do I do about that?
    state.TimeoutWaitHandle.Unregister(asyncResult.AsyncWaitHandle);
}

正确的处理方法是什么?我还需要担心AsyncWaitHandle的
注销()?如果是这样,是否有一种相当简单的方法等待设置

如果I/O在超时之前完成,则无需取消注册,因为正是完成发出回调信号。事实上,在阅读Unregister方法的文档时,似乎完全没有必要调用它,因为您只执行了一次,并且您没有在不相关的方法中注销

如果在执行Unregister时回调方法正在进行,则在回调方法完成之前不会通知waitObject。特别是,如果回调方法执行Unregister,则在该回调方法完成之前不会通知waitObject


是的,你和其他人都有这个问题。IO是否同步完成并不重要。回调和赋值之间仍然存在竞争。Microsoft应该自动为该回调函数提供
RegisteredWaitHandle
。那会解决一切问题的。哦,就像他们说的,事后诸葛亮总是20-20

您需要做的是一直读取
RegisteredWaitHandle
变量,直到它不再为null。在一个紧密的循环中这样做是可以的,因为比赛非常微妙,循环不会旋转很多次

private void RunQuery(QueryState queryState)
{
  // Start the operation.
  var asyncResult = queryState.Query.BeginExecuteSegmented(NoopAsyncCallback, queryState);

  // Register a callback.
  RegisteredWaitHandle shared = null;
  RegisteredWaitHandle produced = ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle,
    (state, timedout) =>
    {
      var asyncResult = opState as IAsyncResult;
      var state = asyncResult.AsyncState as QueryState;
      while (true)
      {
        // Keep reading until the value is no longer null.
        RegisteredWaitHandle consumed = Interlocked.CompareExchange(ref shared, null, null);
        if (consumed != null)
        {
          consumed.Unregister(asyncResult.AsyncWaitHandle);
          break;
        }
      }
    }, asyncResult, queryTimeout, true);

  // Publish the RegisteredWaitHandle so that the callback can see it.
  Interlocked.CompareExchange(ref shared, produced, null);
}

您是否尝试插入“代码>线程”?在中间睡眠“/CODE”,以允许I/O操作的时间完成,看看会发生什么?我没有。我想我只见过这种情况发生过3-4次,然后只出现在重载生产机器上。我不想开始在我的实际代码中添加Sleep()调用。当然,在您的开发环境中测试它。当你说你只见过这种情况发生3-4次,你看到了什么?随机未解释的NullPointerException?确切地说,是Unregister()行上的NullPointerException。既然它提供了这样一个选项,为什么不在
BeginExecuteSegmented
中设置回调?否则,我看不出如何避免这种可能的争用情况。我想引用的文档是说它不会向我的操作的WaitHandle发出信号。它似乎没有提到我从ThreadPool获得的RegisteredWaitHandle。但这太令人困惑了,所以我可能错了。关于RegisterWaitForSingleObject()的文档明确指出,您应该始终注销(),因此我认为我确实需要在某个地方这样做@breischl QuerySegmentCompleted仅作为来自RegisterWaitForSingleObject的回调调用?顺便说一句,这是一个很好的问题。如果state.TimeoutWaitHandle为null,您可能会将WaitHandle的注销留给非最佳的终结器线程。同时,文件上说你应该注销,这在你的情况下是不可能的。我想我明白你的意思了。是否真的需要两个RegisteredWaitHandle变量和CompareExchange()?既然指针赋值是一个原子操作,我就不能检查回调中产生的
变量了吗?@breischl:嗯,你需要一个内存屏障,这样每次迭代都能“重新读取”变量。您可能可以忽略
Interlocked.CompareExchange
调用,因为我确信
RegisteredWaitHandle.Unregistered
也会生成内存障碍。但是,就我个人而言,我会坚持这种模式,因为
是互锁的。compareeexchange
使它变得明确。将
生成的
声明为
volatile
不会有同样的效果吗?@breischl:当然,除了不能将局部变量声明为
volatile
。但是,你的想法是对的。当然,您可以重新构造代码,以便使用
volatile
。有很多有效的方法可以做到这一点。啊,对了,当。顺便说一下,谢谢你回答我的许多后续问题。再加两个(我想):一个
Thread.volatireRead()
就足够了吗?另外,是否值得执行
Thread.Yield()
while(true)
循环的内部操作?