我正在使用其Modbus协议向硬件控制器编写C#包装器 控制器有12个输入和12个输出。
包装器有两个任务:
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。当没有运行序列时,它进行了轮询。
挑战:
对包装器的新需求是它可以在运行序列时检测控制器输入的变化
我修改了包装器,有2个背景工作者,一个用于轮询,另一个用于设置输出寄存器
每个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 :(得分: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 );
}
}
或者沿着这些方向的东西(但是有适当的错误处理!)
只需处理定时器即可处理终止和取消。
答案 1 :(得分: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;
}
}