C# 我的线程设计是否正常?

C# 我的线程设计是否正常?,c#,multithreading,thread-safety,C#,Multithreading,Thread Safety,我是线程方面的新手,我是一名初级开发人员:),所以我猜有很多错误。我的设想是: 查看数据库,如果这些数据将被发送,则获取这些数据 将此数据添加到队列中 如果队列不为空,则下一个mesaj退出队列并发送 等待10秒,从另一个线程发送msj 如果msj停止等待并传递到队列中的下一个消息 如果msj未出现,请尝试再次发送并等待10秒2次 如果仍然没有消息传递到下一个消息,直到msgs完成 然后再次查看数据库中的MSG 我试着这样做: private Thread ReceiveThread

我是线程方面的新手,我是一名初级开发人员:),所以我猜有很多错误。我的设想是:

  • 查看数据库,如果这些数据将被发送,则获取这些数据
  • 将此数据添加到队列中
  • 如果队列不为空,则下一个mesaj退出队列并发送
  • 等待10秒,从另一个线程发送msj
  • 如果msj停止等待并传递到队列中的下一个消息
  • 如果msj未出现,请尝试再次发送并等待10秒2次
  • 如果仍然没有消息传递到下一个消息,直到msgs完成
  • 然后再次查看数据库中的MSG
我试着这样做:

    private Thread ReceiveThread;
    private Thread SendThread;
    internal static Thread ServiceThread;
这是3条线

    ReceiveThread = new Thread(ReceiveTask);
    ReceiveThread.Start();
    ServiceThread = new Thread(SerAutoThread.SendServiceMsg);
    ServiceThread.Start();
    SendThread = new Thread(SendTask);
    SendThread.Start();

它是否线程安全。我不确定是设计出了问题,还是我在其他地方做错了?
谢谢…

看起来你错过了一些角球案例,你可以获得比赛条件。例如,
SerAutoThread
可以向
MessageSendQueue
写入一个包,在
SerAutoThread
甚至开始等待之前(不太可能,但可能)会立即确认该包。这只会导致意外延迟,而不会导致故障

但是,另一种情况是,如果
ReceiveTask
SerAutoThread
已放弃等待并已发送下一条消息之后收到确认。在这种情况下,
SerAutoThread
会认为,
ReceiveTask
在确认前一条消息时,只是确认了新消息。您可能需要为您的消息提供ID以防止这种情况发生,这样您就可以准确地知道哪条消息正在被确认

编辑:根据您的评论,我将再次查看代码,以确定是否存在死锁或活锁风险。我假设您的
MessageSendQueue
MessageReceiveQueue
是阻塞生产者-消费者样式队列的实例,类似于基于方法名称和参数的队列。我还假设线程不是由于异常而被终止的,因为(我真诚地希望)您会注意到这一点

让我们从
SendThread
开始,因为它最容易分析;从线程的角度来看,这里基本上没有什么会出错的地方,尽管有一种方法可以干净地关闭它会很好。只要将内容发布到队列中(并且
PushData
不会以某种方式阻塞),此线程最终将发送它

ReceiveThread
遵循从队列中消费项目的相同(良好、安全)模式,但它也通过共享变量和监视器与
ServiceThread
通信-不太安全且级别很低。假设我们看到了对
\u locker
对象的所有引用,这里没有死锁的风险,因为没有代码在持有
\u locker
锁时等待任何其他操作。总之,只要队列中有消息可用,该线程也将继续执行其任务

但是,像您这样设置
sendingTime
是一种数据竞争,可能会导致
ServiceThread
中出现意外行为。这是因为对
sendingTime
的更改可能随时发生,例如,在检查
if(sendingTime<3)
和下一行的增量之间,留给您的是+1。还有其他一些古怪的事情可能会发生。当您从两个线程访问同一个变量时,始终需要确保有适当的同步

但是这会导致
ServiceThread
锁定吗?假设
2
,我真的不知道怎么做。
SetNextSerAndSend()
中的循环在丢弃当前的
服务之前最多运行四次,每次运行最多等待10秒,因此它应该一直向前运行


看来,我们仍然可以进入一种情况下,没有什么有用的事情可以发生了。如果sendingTime成为既不低于3也不传输的值,则它似乎不再被设置。
SetNextSerAndSend()
中的循环将始终执行else分支并立即跳到下一条消息。在我看来,else分支中的
sendingTime
也必须重置为0才能防止出现这种情况。请注意,这将允许发送再次向前移动,但在同步对
sendingTime

的所有访问之前,您的程序将不会是线程安全的。您可以查看TPL、反应式扩展或TPL数据流。在那里你可以找到安全的方法。考虑异常处理等。看看这些库,并使用它们来解决您的问题。我认为这更适合于?实际上,我给我的MSG提供了id。这不是我的全部代码(为了理解)。当我运行这个应用程序时,它工作得很好,昨天只是我的数据库管理系统(Firebird)出现了故障。然后我停止我的应用程序,停止firebird服务器,然后再次启动并再次运行我的应用程序。现在又好了。我使用的线程不是非常不正确的。所以我担心线程安全。可能是线程锁定的firebird吗?我扩展了我的答案,很抱歉,如果它很冗长,但我在编写代码的同时,试图分析您的代码,加上我很累。然而,我可能已经发现了您的代码是如何实现livelock的(也就是说,在没有死锁的情况下继续运行,但不再做任何有用的事情)。
 class SerAutoThread
    {
    internal static object[] NextService;
    public static readonly object _locker = new object();
    internal static Queue<object[]> Services;
    internal static int sendingTime = 0;
    private static DatabaseFirebird DB;
    internal static void SendServiceMsg()
    {
        DB = new DatabaseFirebird();
        DB.Open(ConnectionStr);
        Services = new Queue<object[]>();
        while (true)
        {
            if (Services.Count != 0)
            {
                SetNextSerAndSend();
            }
            else
            {
                CheckAndSetServices();
            }
        }
    }


     private static void SetNextSerAndSend()
    {
        NextService = Services.Dequeue();
        for (int j = 0; j < 4; j++)
            {
                if (sendingTime == TRANSMITTED)
                {
                   //pass to next msg
                   sendingTime = 0;
                   j = NEXTMSG;
                }
                else if (sendingTime < 3)
                {
                    sendingTime++;
                    Byte[] data = SetNextPckage();
                    DeviceManager.MessageSendQueue.PostItem(new SendMessage("UDPCmd",
                NextService[(int)NextMsg.DeviceId].ToString(), 
                data, data.Length));
                    MyDebug.WriteLine("Sended...");
                    lock (_locker)
                    {
                        Monitor.Wait(_locker, TimeSpan.FromSeconds(10));
                    }
                }
                else 
                {
                    // pass to next msg
                    j = NEXTMSG;
                }
            }

    }
}
private void ReceiveTask()
        {
            ReceiveMessage receiveMsg;
            while (true)
            {
                receiveMsg = Com.MessageReceiveQueue.GetItem(-1);
                SerAutoThread.sendingTime
                        = SerAutoThread.TRANSMITTED;
                    lock (SerAutoThread._locker)
                    {
                        Monitor.Pulse(SerAutoThread._locker);
                    }

            }
        }
 private void SendTask()
        {
            SendMessage msg;
            while (true)
            {
                msg = MessageSendQueue.GetItem(-1);
                String rtrn = PushData(msg);
            }

        }