0

我正在写一个C#包装,使用其Modbus协议的硬件控制器。
该控制器有12个输入和12个输出。同步两个BackgroundWorkers

包装有两个任务:
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 On和将其关闭后500毫秒。
操作之间的暂停使用Thread.Sleep()实现。
以前我只用过一个BackgroundWorker。当不运行序列时,它执行轮询。

挑战:
为包装的新要求是运行的序列,同时它可以检测控制器的输入的变化。
我已经修改包装有2个backgroundworkers,一个用于轮询,另一个用于设置输出寄存器。
每个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; 
} 

问题:
这似乎是两个BackgroundWorkers互相干扰。我试过使用Semaphore,Monitor.Wait()ManualResetEvent.WaitOne(),但处理序列的BackgroundWorker不能很好地处理它们。主要问题 - 睡眠持续时间与以前不一致。

任何建议将受到欢迎。

+0

我会尝试在一个线程与状态机来解决这个问题。例如。创造一个大循环,它永远在里面运行,根据状态你需要做的是什么。发送一个序列,检查输出等。 –

+0

他们如何“互相干扰”,为什么你需要同步? –

+0

当仅使用一个线程时,我无法轮询输入寄存器,因为'Thread.Sleep()'函数会锁定它。我已经在上面添加了一些代码。 – toy4fun

回答

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

或者类似的规定(但具有合适的错误处理! )

您可以通过处置定时器来处理终止和取消。


BTW,有关于线程,你可以读一个很好的(而且免费)电子书:

Albahari: Threading in C#

+0

感谢您的代码。尝试了你的方法,但仍然面临同步问题。 看起来好像我在向控制器发送一条消息的同时还没有为之前的消息得到答案,它的粉碎。例外情况是:'由于线程退出或应用程序请求,I/O操作已被中止。' – toy4fun

+0

@ toy4fun我已将互斥添加到我的答案中。 –

+0

它现在工作完美。我已经使用2个BackgroundWorkers解决方案尝试了相同的'Lock()'机制,并且它也可以工作。我只需要决定哪个解决方案被视为最佳实践 - BackgroundWorkers vs. Timers。再次感谢。 – toy4fun

0

如何将序列名排队到等待BlockingCollection的一个线程?

伪,每个设备通道一个线程:

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

嗯..这将无法正常工作,因为它可能会轮询过于频繁时,被发送的序列。需要重新思考,但我仍然认为每个设备的一个线程将会是更好的方法。

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; 
    } 
} 
+0

感谢您的建议。这与我使用一个BackgroundWorker的第一个解决方案类似。这里的问题是,我不知道DoPoll函数耗费了多少时间,但是我需要保持输出寄存器之间的精确时间间隔。这就是为什么我选择了2个BackgroundWorker体系结构。 – toy4fun

+0

WTF?我试图更新我的代码,但代码块无法正常工作,我发布了一堆gunge! –

+0

为每个标签级别使用4 + 4空格:) –