1

最初に入力されたスレッドは、どのようにして他の並行スレッドに同じメソッドの終わりを知らせることができますか?

PollDPRAM() という名前のメソッドがあります。ネットワークを介して低速のハードウェアに移動し、オブジェクトのプライベート データを更新する必要があります。同じメソッドが他のスレッドによって同時に呼び出された場合、それらはトリップを実行する必要はありませんが、データが新鮮であるため、最初に来るスレッドがジョブを完了するのを待ってから終了する必要があります (たとえば、10 ~ 30 ミリ秒前でも違いはありません)。 . 2番目、3番目などのスレッドが最初に入力されていない方法で検出するのは簡単です. インターロック カウンターを使用して同時実行性を検出します。

問題: 最初のスレッドの終了を検出するために、カウンター (Interlocked.Read) を監視して、n>1 スレッドの開始時に検出された値よりも小さい値にカウンターが減少したことを検出するという不適切な選択をしました。最初のスレッドは、メソッドが終了した直後にメソッドに再び入る可能性があるため、この選択は不適切です。したがって、n>1 スレッドはカウンターのディップを検出しません。

質問: 最初に入力されたスレッドがメソッドを終了したことを正しく検出するには、この最初のスレッドがすぐに再度入力できる場合でも?

ありがとうございました

PSコードの一部

        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));
            }
        }
    }
4

3 に答える 3

1

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

基本的に、メソッドは lock(lockobj) { CallMethod() } でラップできます

すべてのスレッドが同じプロセスにあると仮定すると、これにより保護が得られます。

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

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

もちろん、これは、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.");
            }
        }
    }
于 2011-01-14T22:33:30.263 に答える
1

メソッドを同期し、メソッド内で、ネットワーク アクセスが最後に実行された時刻の記録をチェックして、もう一度実行する必要があるかどうかを判断します。

于 2011-01-14T22:29:45.427 に答える
0

これを行うには、さまざまな方法があります。誰かがすでに述べたように、クリティカルセクションを使用できますが、他のスレッドがブロックされている場合、「ただ終了する」という動作は得られません。そのためには、ある種のフラグが必要です。volatile bool を使用してその bool のアクセスをロックするか、1 つのカウントでセマフォを使用することができます。最後に、ミューテックスを使用できます。同期オブジェクトを使用する利点は、WaitForSingleObject を実行してタイムアウトを 0 に設定できることです。次に、待機が成功したか (成功した場合は最初のスレッドが終了したか)、失敗したか (最初のスレッドが終了した場合) を確認できます。まだ実行されています)。

于 2011-01-14T22:50:56.783 に答える