2011-01-14 4 views
1

最初に入力したスレッドは、同じメソッドの終了を他の同時スレッドに通知できますか?最初に入力されたスレッドは、同じメソッドの終了を他の同時スレッドに通知できますか?

私は、PollDPRAM()という名前のメソッドを持っています。遅いハードウェアへのネットワーク経由でのトリップを行い、オブジェクトのプライベートデータを更新する必要があります。同じメソッドが他のスレッドから同時に呼び出された場合、データは新鮮であるため、最初のスレッドが完了して終了するまで待機してはいけません(10-30ミリ秒前に違いはありません) 。 2番目、3番目などのスレッドが最初に入力されないメソッドで検出が簡単です。 Interlockedカウンタを使用して並行性を検出します。

問題:n> 1スレッドの入口で検出された値よりも小さい値へのカウンタが減少した後に、カウンタ(Interlocked.Read)が監視するように見て、最初のスレッドの終了を検出できませんでした。選択が悪いのは、最初のスレッドが、メソッドが終了した直後に再度メソッドに再入力できるためです。したがって、n> 1のスレッドは決してカウンタでディップを検出しません。

質問: この最初のスレッドが即座に再び入力できる場合でも、最初に入力されたスレッドを正しく検出する方法はありますか?

ありがとうございました

P.S.コード

 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 <= 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] & 0x80) == 0x80; 
          if (canReadMotors) break; 
          retryCount++; 
          Thread.Sleep(1); 
         } while (retryCount < 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) && ((DateTime.Now - start).TotalMilliseconds < 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) < 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 < 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 > 50 || parallelThreads > 1 || retryCount > 0) 
      { 
       Trace.WriteLine(string.Format("Controller {0}, DPRAM poll {1:0} ms, threads {2}, retries {3}", 
        this.controller.number, 
        duration.TotalMilliseconds, 
        parallelThreads, 
        retryCount)); 
      } 
     } 
    } 

答えて

0

あなたがこれを行う方法はたくさんありますが、誰かがすでに述べたようにクリティカルセクションを使うことはできますが、他のスレッドがブロックしている場合には "ただ終了"という振る舞いを与えません。あなたは揮発性のブールに行き、そのブールへのアクセスを回避するか、または単一のカウントでセマフォを使用することができます。最後に、ミューテックスを使用することができます。同期オブジェクトを使用するメリットは、WaitForSingleObjectを実行してタイムアウトを0に設定できることです。次に、待機が成功したかどうかを確認できます(最初のスレッドが終了した場合)。まだ実行されています)。

+0

終了するだけでは機能しません。ポーリング方法は、あらゆる年齢のデータに依存しなければならないすべての実際のデータ使用方法の前にあるためです。 15msまたはyongerと言ってください –

+0

ああ、最初のスレッドが実行されていたかどうか気にするだけでなく、データが一定の年齢だった場合も気にしないでください。最後に、変数を必要とします(同期アクセスを使用して)。 – SRM

+0

ありがとうございます。年齢チェック付きの単純なロックが私のために働いた。申し訳ありませんが金曜日の夜のビルドだった –

1

のワンピースは、Synchronizeメソッドとメソッド内ネットワークへのアクセスは、それが再び行われる必要があるかどうかを判断するために行われ、最後のあった時間の記録を確認してください。

+0

私は、不要なトリップを除いて、可能な限りネットワークアクセスを選択しました。スレッドの衝突は、トリップが不要な唯一の状況です。だから、単純化のためには、論理的にあなたが陳腐するデータを検討したいとその前に、それは十分に良いことだそのあと、おそらくミリ秒単位で測定されたいくつかの時間が、そこにある –

+0

タイムスタンプのような余分なデータを避けることをお勧めします。クリティカルセクションに入るスレッドは、データが十分に新鮮であるかどうかに基づいて、ネットワーク検索を行うかどうかを判断する必要があります。これは、各検索時にリアルタイムのミリ秒を記録するのと同じくらい簡単です(Javaでは、System.currentTimeMillisで行われますが、C#では非常によく似ている可能性があります)、クリティカルセクションの開始時にチェックします。 –

1

"lock"キーワードでサポートされているC#モニタクラスを使用できます。

基本的にあなたの方法は、ロックにラップできる(lockobj){CallMethod()}

これは、すべてのスレッドが同じプロセスであると仮定すると、あなたの保護を提供します。

プロセス間でロックする必要がある場合は、ミューテックスを使用する必要があります。

私のプログラムでは、あなたのメソッドに静的なタイムスタンプとキャッシュされた値を入れます。だから、メソッドが入ると、タイムスタンプが許容範囲内であれば、キャッシュされた値を返し、そうでなければ単にフェッチを実行します。ロック機構と組み合わせることで、必要な作業を行うことができます。

もちろん、これは、C#モニタで撮影してブロックする時間がアプリケーションのパフォーマンスに影響しないことを前提としています。

更新: キャッシュとタイムスタンプを使用する意味を示すためにコードを更新しました。私はあなたの "motorsData"変数がモーターポーリングから返されるものであると仮定しています。そのため、変数がありません。私は「誤解ましただし、単純に注意してください。それがコードから返された後にデータを格納する変数を追加し、私はあなたの例外に対処する必要があるので、あなたのためのチェックエラーを行っていない。

static DateTime lastMotorPoll; 
    const TimeSpan CACHE_PERIOD = new TimeSpan(0, 0, 0, 0, 250); 
    private object cachedCheckMotorsDataLock = new object(); 

    private void CachedCheckMotorsData() 
    { 
     lock (cachedCheckMotorsDataLock) //Could refactor this to perform a try enter which returns quickly if required 
     { 
      //If the last time the data was polled is older than the cache period, poll 
      if (lastMotorPoll.Add(CACHE_PERIOD) < DateTime.Now) 
      { 
       pollMotorsData(); 
       lastMotorPoll = DateTime.Now; 
      } 
      else //Data is fresh so don't poll 
      { 
       return; 
      } 
     }  
    } 

    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; 
     try 
     { 
      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] & 0x80) == 0x80; 
         if (canReadMotors) break; 
         retryCount++; 
         Thread.Sleep(1); 
        } while (retryCount < 10); 
        if (!canReadMotors) 
        { 
         throw new DeltaTauControllerException(this.controller, "Timeout waiting on DPRAM Foreground Handshaking Bit"); 
        } 
        // 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) && ((DateTime.Now - start).TotalMilliseconds < 255)); 

      // Assert that result is live 
      if (!changeDetected) 
      { 
       throw new DeltaTauControllerException(this.controller, "DPRAM Background Data timestamp is not updated. DPRAM forground handshaking failed."); 
      } 
     } 
    } 
+0

はい。クリティカルセクションを順番に調べると、すべての投票で更新が行われます。しかし、クリティカルセクションではネットワークへの負荷が軽減されません。設計目標は、周辺部品のゆっくりとした寿命を緩和することです。アルゴリズムは、旅行を行うには、メソッドの内側に分岐する必要がありますか旅行をしないでください。 –

+0

100%がSpenceに同意します。私はちょうどそれが何を起こっているのかを知らせるためにコールバックイベント通知メカニズムを提供することはいいです。しかし、これはWindows Workflow FoundationとMVCのものです。 – Nickolodeon

+0

ネットワークに負荷をかけるために、ネットワーククエリコードが実行される前にThread.Sleep(ms)命令を提供するだけです。ニコロデオン。 – Nickolodeon

関連する問題