我正在使用多个线程将串行端口数据发送到机器人。
当我在BotNavigation
上调用导航方法时,它们不会彼此同步。
SerialPort port = new SerialPort("COM3");
BotNavigation bot = new BotNavigation();
// must execute three times, but it only executes once.
bot.TakeForward(3, port);
// must execute once, and it works fine.
bot.TurnLeft(1, port);
你能帮我解决问题吗?
这是代码......
namespace NavigateBot
{
class BotNavigation
{
[MethodImpl(MethodImplOptions.Synchronized)]
public void TakeForward(int distanceTime, SerialPort port)
{
int count = 0;
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(() =>
{
while (true)
{
port.WriteLine("F");
if (count >= distanceTime)
{
Thread.CurrentThread.Abort();
break;
}
Thread.Sleep(distanceTime * 250);
count++;
}
}));
thread.IsBackground = true;
thread.Start();
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void TakeBackward(int distanceTime, SerialPort port)
{
int count = 0;
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(() =>
{
while (true)
{
port.WriteLine("B");
if (count >= distanceTime)
{
Thread.CurrentThread.Abort();
break;
}
Thread.Sleep(distanceTime * 250);
count++;
}
}));
thread.IsBackground = true;
thread.Start();
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void TurnLeft(int distanceTime, SerialPort port)
{
int count = 0;
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(() =>
{
while (true)
{
port.WriteLine("L");
if (count >= distanceTime)
{
Thread.CurrentThread.Abort();
break;
}
Thread.Sleep(distanceTime * 250);
count++;
}
}));
thread.IsBackground = true;
thread.Start();
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void TurnRight(int distanceTime, SerialPort port)
{
int count = 0;
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(() =>
{
while (true)
{
port.WriteLine("R");
if (count >= distanceTime)
{
Thread.CurrentThread.Abort();
break;
}
Thread.Sleep(distanceTime * 250);
count++;
}
}));
thread.IsBackground = true;
thread.Start();
}
}
}
答案 0 :(得分:2)
您正在使用[MethodImpl(MethodImplOptions.Synchronized)]
同步方法,但这些方法会启动自己的线程以将数据发送到串行端口。
线程内发生的操作未同步,因为原始方法调用(启动线程)已经返回并释放[MethodImpl(MethodImplOptions.Synchronized)]
创建的锁。
如果您希望机器人的动作按顺序执行,您可以在自己的私有锁对象上同步线程本身。
进一步的建议:
Thread.Abort()
,只需使用count < distanceTime
作为while
循环条件。像这样:
namespace NavigateBot
{
class BotNavigation
{
private readonly object _serialLock = new object();
[MethodImpl(MethodImplOptions.Synchronized)]
public void TakeForward(int distanceTime, SerialPort port)
{
int count = 0;
ThreadPool.QueueUserWorkItem(
delegate
{
lock (_serialLock)
{
while (count < distanceTime)
{
port.WriteLine("F");
Thread.Sleep(distanceTime*250);
count++;
}
}
});
}
...
[MethodImpl(MethodImplOptions.Synchronized)]
public void TurnLeft(int distanceTime, SerialPort port)
{
int count = 0;
ThreadPool.QueueUserWorkItem(
delegate
{
lock (_serialLock)
{
while (count < distanceTime)
{
port.WriteLine("L");
Thread.Sleep(distanceTime*250);
count++;
}
}
});
}
...
}
}
答案 1 :(得分:1)
首先,你需要像瘟疫一样避免Thread.Abort()
。像这样重写你的一个方法将是避免它的简单方法:
public void TakeBackward(int distanceTime, SerialPort port)
{
int count = 0;
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(() =>
{
while (true)
{
port.WriteLine("B");
if (count >= distanceTime)
{
break;
}
else
{
Thread.Sleep(distanceTime * 250);
count++;
}
}
}));
thread.IsBackground = true;
thread.Start();
}
我已经说过,我将建议一种替代方法,使这段代码能够很好地为您服务。
我建议您使用Microsoft的Reactive Framework(NuGet&#34; Rx-Main&#34;)。
以下是代码的样子:
class BotNavigation
{
private EventLoopScheduler _eventLoop = new EventLoopScheduler();
private void Take(string message, int distanceTime, SerialPort port)
{
Observable
.Interval(TimeSpan.FromMilliseconds(250.0))
.Select(x => message)
.StartWith(message)
.Take(distanceTime)
.ObserveOn(_eventLoop)
.Subscribe(port.WriteLine);
}
public void TakeForward(int distanceTime, SerialPort port)
{
this.Take("F", distanceTime, port);
}
public void TakeBackward(int distanceTime, SerialPort port)
{
this.Take("B", distanceTime, port);
}
public void TurnLeft(int distanceTime, SerialPort port)
{
this.Take("L", distanceTime, port);
}
public void TurnRight(int distanceTime, SerialPort port)
{
this.Take("R", distanceTime, port);
}
}
EventLoopScheduler
基本上是一个后台线程,它将提供您需要的所有同步。
observable创建了您需要的时间和消息。
Observable
// fire a timer every 250 ms
.Interval(TimeSpan.FromMilliseconds(250.0))
// change the timer output to the message
.Select(x => message)
// at the start instantly output a message
.StartWith(message)
// only take `distanceTime` messages
.Take(distanceTime)
// execute the output on the synchronised event loop thread
.ObserveOn(_eventLoop)
// write the value to the port
.Subscribe(port.WriteLine);
希望这很容易理解。
答案 2 :(得分:0)
使用[MethodImpl(MethodImplOptions.Synchronized)]
的Thr同步属性仅同步您的方法,但不同步那些方法中启动的线程。
当你调用botForward.TakeForward(3, port);
时,这会阻止任何麻烦的线程在同一个BotNavigation实例上同时调用此方法,但是这个方法运行得非常快,甚至不能说是内部线程在方法返回并释放锁之前,此方法甚至会调用一次Port.Writeline。
此外,您的线程在当前状态下使用不受保护的上下文变量运行:所有线程的SerialPort port
在此类的同一实例上工作,只要SerialPort本身不是线程安全的,这是不安全的。
您的代码的目标尚不明确,但如果您将代码拆分为2个线程,您可能会更安心:一个为您的机器人提供命令,另一个用于保存您的通信类实例的其他线程,以及以确定的方式处理命令(如线程安全队列)