同步两个后台工作者
本文关键字:两个 后台 工作者 同步 | 更新日期: 2023-09-27 17:54:59
我正在写一个c#包装器,使用它的Modbus协议到硬件控制器。
控制器有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;
}
问题:
这两个后台工作者好像在互相干扰。我试过使用Semaphore
, Monitor.Wait()
和ManualResetEvent.WaitOne()
,但处理序列的BackgroundWorker不能很好地处理它们。主要问题是——它的睡眠时间不像以前那样一致。
在测试代码之外使用Thread.Sleep
通常是不理想的。
您可以使用System.Threading.Timer
对象来满足这两个需求。
EDIT如果你想防止并发调用,你可以使用锁来实现互斥。
创建一个可以被两个定时器处理程序看到的对象:
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#
如何将序列名称排队到等待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;
}
}