MethodImplOptions.Synchronized在C#.Net中不工作

MethodImplOptions.Synchronized在C#.Net中不工作,c#,.net,multithreading,C#,.net,Multithreading,我使用多个线程向机器人发送串行端口数据 当我在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, an

我使用多个线程向机器人发送串行端口数据

当我在
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();
        }
    }
}

您正在使用
[MethodImpl(methodimpoptions.Synchronized)]
同步这些方法,但这些方法会启动自己的线程将数据发送到串行端口

线程内发生的操作未同步,因为原始方法调用(启动线程)已返回并释放由
[MethodImpl(methodimpoptions.synchronized)]
创建的锁

如果希望机器人的动作按顺序执行,可以在线程自己的私有锁对象上同步线程本身

进一步建议:

  • 无需调用
    Thread.Abort()
    ,只需使用
    count
    作为
    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++;
                    }
                }
            });

        }

        ...
    }
}
namespace-NavigateBot
{
类导航
{
私有只读对象_serialLock=新对象();
[MethodImpl(MethodImplOptions.Synchronized)]
public void TakeForward(int distanceTime,SerialPort)
{
整数计数=0;
ThreadPool.QueueUserWorkItem(
代表
{
锁(_serialLock)
{
while(计数<距离时间)
{
书面港(“F”);
线程睡眠(距离时间*250);
计数++;
}
}
});
}
...
[MethodImpl(MethodImplOptions.Synchronized)]
公共无效左转(整数距离时间,串行端口)
{
整数计数=0;
ThreadPool.QueueUserWorkItem(
代表
{
锁(_serialLock)
{
while(计数<距离时间)
{
写线港(“L”);
线程睡眠(距离时间*250);
计数++;
}
}
});
}
...
}
}

您使用的Thr同步属性
[MethodImpl(methodimpoptions.Synchronized)]
仅同步您的方法,而不同步在这些方法中启动的线程

当您调用
botForward.TakeForward(3,端口)时
这会阻止任何pother线程在同一BotNavigation实例上同时调用此方法,但此方法运行速度非常快,甚至不能说此方法的内部线程在方法返回和释放锁之前调用Port.Writeline一次

此外,线程在当前状态下使用未受保护的上下文变量运行:
SerialPort-port
所有线程都在此类的同一实例上工作,这是不安全的,只要SerialPort本身不是线程安全的


代码的目标还不清楚,但如果您将代码分成两个线程,您可能会节省:一个线程向bot发出命令,另一个线程保存通信类的实例,并以确定性方式处理命令(如线程安全队列)

首先,您确实需要避免像瘟疫一样的
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();
    }
话虽如此,我将建议一种替代方法,使这段代码能够很好地为您工作

我建议您使用微软的反应式框架(NuGet“Rx Main”)

下面是代码的样子:

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
            // 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);
可观察对象创建您需要的时间和消息

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

希望这很容易理解。

不要调用
Thread.Abort()
-请注意,您的方法是同步的,但是它们会启动额外的线程,而这些线程在任何情况下都不会同步。从根本上说,不清楚您想要实现什么,但这看起来不是正确的方法…@Enigmativity在自己的线程中调用abort是安全的。但我大体上同意你的看法。在这种情况下,它是安全的。@SriramSakthivel-我甚至不认为它是安全的。唯一安全的时候是关闭程序时。@Enigmativity为什么?为什么你认为当一个线程自动中止时是不安全的?对不起。我使用的是Console.Out进行调试,而不是SerialPort。请尝试上面更新的代码。不客气。如果这个答案解决了你的问题,你可以通过接受答案并投票来感谢我,并给我打分。