title: How first entered thread can signal to other concurrent threads the end of same method?
body: <p>How first entered thread can signal to other concurrent threads the end of same method ?</p>
  
  <p>I have method named say PollDPRAM(). It must make a trip over network to some slow hardware and refresh object private data. If the same method is simultaneously called by other threads, they must not do the trip, but wait for first coming thread to complete the job and simply exit, because the data is fresh (say 10-30 ms ago does not make a difference).
  Its easy to detect in method that second, 3rd etc threads are not first entered. I  use Interlocked counter to detect concurrency.</p>
  
  <p>Problem: I made a poor choice to detect the exit of first thread by watching the counter (Interlocked.Read) to watch after the decrease of counter to value less than it was detected at entrance of n>1 thread. The choice is bad, because the first thread can reenter the method again nearly immediately after it leaves. So the n>1 threads will never detect dip in counter.</p>
  
  <p>So question:
  How to correctly detect that first entered thread has exited the method, even if this first thread can immediately enter it again ?</p>
  
  <p>Thank you</p>
  
  <p>P.S. Piece of code</p>
  
  <pre><code>        private void pollMotorsData()
      {
          // Execute single poll with "foreground" handshaking 
          DateTime start = DateTime.Now;
          byte retryCount = 0;
          // Pick old data atomically to detect change
          uint motorsDataTimeStampPrev = this.MotorsDataTimeStamp;
          bool changeDetected = false;
          // The design goal of DPRAM is to ease the bottleneck
          // Here is a sensor if bottleneck is actually that tight
          long parallelThreads = Interlocked.Increment(ref this.motorsPollThreadCount);
          try
          {
              // For first thread entering the counter will be 1
              if (parallelThreads &lt;= 1)
              {
                  do
                  {
                      // Handshake signal to DPRAM write process on controller side that host PC is reading
                      this.controller.deltaTauTcpClient.Pmac_SetBit(OFFSET_0x006A_BIT15_FOREGROUND_READ, 15, true);
                      try
                      {
                          bool canReadMotors = false;
                          byte[] canReadFrozenDataFlag = new byte[2];
                          do
                          {
                              this.controller.deltaTauTcpClient.Pmac_GetMem(OFFSET_0x006E_BIT15_FOREGROUND_DONE, canReadFrozenDataFlag);
                              canReadMotors = (canReadFrozenDataFlag[1] &amp; 0x80) == 0x80;
                              if (canReadMotors) break;
                              retryCount++;
                              Thread.Sleep(1);
                          } while (retryCount &lt; 10);
                          if (!canReadMotors)
                          {
                              throw new DeltaTauControllerException(this.controller, "Timeout waiting on DPRAM Foreground Handshaking Bit");
                          }
                          // The lock is meaningless in contructor as it is certainly single threaded
                          // but for practice sake the access to data should always be serialized
                          lock (motorsDataLock)
                          {
                              // Obtain fresh content of DPRAM
                              this.controller.deltaTauTcpClient.Pmac_GetMem(OFFSET_0x006A_394BYTES_8MOTORS_DATA, this.motorsData);
                              this.motorsDataBorn = DateTime.Now;
                          }
                      }
                      finally
                      {
                          // Handshake signal to DPRAM write process on controller side that host PC has finished reading
                          this.controller.deltaTauTcpClient.Pmac_SetBit(OFFSET_0x006A_BIT15_FOREGROUND_READ, 15, false);
                      }
                      // Check live change in a separate atom
                      changeDetected = this.MotorsDataTimeStamp != motorsDataTimeStampPrev;
                  } while ((!changeDetected) &amp;&amp; ((DateTime.Now - start).TotalMilliseconds &lt; 255));
                  // Assert that result is live
                  if (!changeDetected)
                  {
                      throw new DeltaTauControllerException(this.controller, "DPRAM Background Data timestamp is not updated. DPRAM forground handshaking failed.");
                  }
              }
              else
              {
                  // OK. Bottleneck ! The concurrent polls have collided 
                  // Give the controller a breathe by waiting for other thread do the job
                  // Avoid aggressive polling of stale data, which is not able to be written, locked by reader
                  // Just wait for other thread do whole polling job and return with no action
                  // because the data is milliseconds fresh
                  do
                  {
                      // Amount of parallel threads must eventually decrease
                      // But no thread will leave and decrease the counter until job is done
                      if (Interlocked.Read(ref this.motorsPollThreadCount) &lt; parallelThreads)
                      {
                          // Return is possible because decreased value of concurrentThreads means that
                          // this very time other thread has finished the poll 1 millisecond ago at most
                          return;
                      }
                      Thread.Sleep(1);
                      retryCount++;
                  } while ((DateTime.Now - start).TotalMilliseconds &lt; 255);
                  throw new DeltaTauControllerException(this.controller, "Timeout 255ms waiting on concurrent thread to complete DPRAM polling");
              }
          }
          finally
          {
              // Signal to other threads that work is done
              Interlocked.Decrement(ref this.motorsPollThreadCount);
              // Trace the timing and bottleneck situations
              TimeSpan duration = DateTime.Now - start;
              if (duration.TotalMilliseconds &gt; 50 || parallelThreads &gt; 1 || retryCount &gt; 0)
              {
                  Trace.WriteLine(string.Format("Controller {0}, DPRAM poll {1:0} ms, threads {2}, retries {3}",
                      this.controller.number,
                      duration.TotalMilliseconds,
                      parallelThreads,
                      retryCount));
              }
          }
      }
  </code></pre>