MethodImplOptions.Synchronized在C#.Net中不起作用

时间:2015-02-24 11:27:47

标签: 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, 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();
        }
    }
}

3 个答案:

答案 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个线程,您可能会更安心:一个为您的机器人提供命令,另一个用于保存您的通信类实例的其他线程,以及以确定的方式处理命令(如线程安全队列)