1

Modbusプロトコルを使用してハードウェア コントローラーに C# ラッパーを作成しています。
コントローラには 12 の入力と 12 の出力があります。

ラッパーには 2 つのタスクがあります
。 1. コントローラの入力を一定の間隔 (つまり 50ms) でポーリングします。
2. コントローラの出力を変更する事前設定されたシーケンスを実行します。

シーケンスは XML ベースです。

<opcode>
  <register>0</register>
  <bit>1</bit>
  <duration>500</duration>
</opcode>
<opcode>
  <register>0</register>
  <bit>0</bit>
  <duration>0</duration>
</opcode>
....

上記のサンプルでは、​​コントローラーは出力 #0 をオンにし、500ms 後にオフにする必要があります。
操作間の一時停止は、 を使用して達成されましThread.Sleep()た。
過去に、BackgroundWorker を 1 つだけ使用しました。シーケンスを実行していないときは、ポーリングを行いました。

課題:
ラッパーに対する新しい要求は、シーケンスの実行中にコントローラーの入力の変化を検出できることです。
ラッパーを変更して、2 つのバックグラウンド ワーカー (1 つはポーリング用、もう 1 つは出力レジスタの設定用) を持つようにしました。
各 BackgroundWorkers はコントローラーで個別の関数を呼び出しますが、お互いのデータにアクセスしようとしたり、データを共有したりすることはありません。

現在のコード:

private void workerSequence_DoWork(object sender, DoWorkEventArgs e)
{
    if (!terminating)
        if (e.Argument != null)
            DoSequence(e);
}

private void workerPoll_DoWork(object sender, DoWorkEventArgs e)
{
    if (!terminating)
    {
        DoPoll();
        Thread.Sleep(pollInterval);
    }
}

private void DoSequence(DoWorkEventArgs e)
{
    string sequenceName = e.Argument.ToString();

    foreach (configurationSequencesSequenceOpcode opcode in sequencesList[sequenceName])
    {
        if (workerSequence.CancellationPending)
            break;

        byte register = opcode.register;
        bool bit = opcode.bit;
        int duration = opcode.duration;

        SetRegister(register, bit, false);
        Thread.Sleep(duration);
    }

    e.Result = e.Argument;
}

問題:
2 つの BackgroundWorker が互いに干渉しているようです。Semaphoreを使用しMonitor.Wait()てみましManualResetEvent.WaitOne()たが、シーケンスを処理する BackgroundWorker がうまく処理できません。主な問題 - スリープ時間は以前と同じではありません。

どんなアドバイスでも大歓迎です。

4

2 に答える 2

1

Thread.Sleepテストコードの外部で使用することは、一般的に理想的ではありません。

System.Threading.Timerこれらの両方の要件にオブジェクトを使用できます。


同時呼び出しを防止したい場合は、ロックを使用して相互排除を実装できます。

両方のタイマーハンドラーで表示できる、ロックするオブジェクトを作成します。

public object gate = new object();

ポーリングでは、次のようなタイマーを設定します。

var pollTimer = new Timer( HandlePoll, null, 0, Timeout.Infinite );
...

void HandlePoll( object state )
{
  lock ( gate )
  {
    DoPoll();
  }
  pollTimer.Change( pollInterval, Timeout.Infinite );
}

Timeout.Infinteタイマーが繰り返されないように期間を設定しました。これpollIntervalは、あるポーリングが終了してから別のポーリングが開始するまでの時間であることを意味します。これは、時間DoPoll()がかかる場合はポーリング期間とは異なります。

このようにすることで、前のポーリングがまだ実行されている間にタイマーが再び作動するのを防ぐこともできます。


同じプリンシパルを使用してコマンドを送信できますが、現在のインデックスをsequencesList:に管理する必要があります。

var seqTimer = new Timer( HandleSequence, null, 0, Timeout.Infinate );
int seqIndex = 0;
...

void HandleSequence( object state )
{
  var opcode = sequencesList[sequenceName][ seqIndex ];

  byte register = opcode.register;
  bool bit = opcode.bit;
  int duration = opcode.duration;

  lock( gate )
  {
    SetRegister(register, bit, false);
  }

  seqIndex++;
  if ( seqIndex < sequencesList[sequenceName].Count )
  {
    seqTimer.Change( duration, Timeout.Infinte );
  }
}

または、それらの線に沿った何か(ただし、適切なエラー処理が必要です!)

タイマーを破棄するだけで、終了とキャンセルを処理できます。


ところで、あなたが読むことができるスレッドについての優れた(そして無料の)電子ブックがあります:

Albahari: Threading in C#

于 2013-01-21T13:42:18.280 に答える
0

BlockingCollection を待機する 1 つのスレッドにシーケンス名をキューに入れるのはどうですか?

疑似、デバイス チャネルごとに 1 つのスレッド:

while(true)
{
    if outputQueue.TryTake(thisSeqName,50)
    {
        foreach (configurationSequencesSequenceOpcode opcode in sequencesList[thisSeqName])
        {
            outputOpcode(opcode);
            DoPoll();
        }
    }
    else
        DoPoll();
}

うーん..シーケンスが送信されているときにおそらく頻繁にポーリングするため、これは正しく機能しません。再考する必要がありますが、デバイスごとに 1 つのスレッドがより良いアプローチになると私はまだ考えています。

int startTick=Environment.TickCount;
while(true){
    int now=Environment.TickCount;
    waitInterval=50-(now-startTick);
    if outputQueue.TryTake(thisSeqName,waitInterval)
    {
        foreach (configurationSequencesSequenceOpcode opcode in sequencesList[thisSeqName])
        {
            outputOpcode(opcode);
            int now=Environment.TickCount;
            waitInterval=50-(now-startTick);
            if (waitInterval<=0)
            {
                DoPoll();
                startTick=Environment.TickCount;
            }
        }
    }
    else
    {
        DoPoll();
        startTick=Environment.TickCount;
    }
}
于 2013-01-21T13:24:20.610 に答える