c# - 多线程进程中静态 Queue.Enqueue 期间意外数据丢失

标签 c# multithreading thread-safety aforge

private Queue<FrameObjectElement> _queueObject = new
Queue<FrameObjectElement>();

private static Queue<FrameObjectElement> _queueItem = new
Queue<FrameObjectElement>();

private static int permitEntryCount = 0;

private int allowThreadEntry = 0;

我有 2 个队列变量,如上所示。

public Camera(IVideoSource source, MotionDetector detector)
{
    VideoSource = source; 
    _motionDetector = detector;
    VideoSource.NewFrame += VideoNewFrame; 
    MainForm.OnRegisterClickedEvent += new MainForm.RegisterClickedEventHandler(MainForm_OnRegisterClickedEvent);
    MainForm.OnReceiveMultipleFrameEvent += new MainForm.ReceiveMultipleFrameEventHandler(MainForm_OnReceiveMultipleFrameEvent);
}

我有一个 Camera 类,上面显示的是构造函数实现的一部分。视频源始终监听事件 VideoNewFrame; 下面我展示的代码是 VideoNewFrame 中的一段代码。

FrameObjectElement frameObj = new FrameObjectElement();
frameObj.cameraID = CW.Camobject.id;
frameObj.cameraTag = _FPGAFrameCount / 2;
frameObj.FirstFrameBuffer = BitmapToRgbValues(twoframe_arg.GetFirstBitmap(352, 288));
frameObj.SecondFrameBuffer = BitmapToRgbValues(twoframe_arg.GetSecondBitmap(352, 288));

if (_queueObject.Count > 5)
    _queueObject.Clear();

_queueObject.Enqueue(frameObj);

if (allowThreadEntry == permitEntryCount && isClear) 
{
    if (_queueObject.Count !=  0)
    {
        lock (this)
        {
            _queueItem.Enqueue(_queueObject.Peek());
        }
        Debug.WriteLine("Thread ID: " + Thread.CurrentThread.ManagedThreadId.ToString() +
        " - " + _queueObject.Count.ToString() +
        " queue in QueueObject : Camera ID : " + _queueObject.Peek().cameraID.ToString() +
        " : Camera Tag : " + _queueObject.Peek().cameraTag.ToString() + 
        " : Queue item count : " + _queueItem.Count.ToString());

        _queueObject.Dequeue();

        if (_queueItem.Count == numberOfCamera && isAllow)
        {
            CurrentID = CW.Camobject.id;
            isAllow = false;
        }

        allowThreadEntry++;
        if (_queueItem.Count == numberOfCamera)
        {
            if (CurrentID == CW.Camobject.id)
            {
                isClear = false;
                //allowEntry = false;

                //Debug.WriteLine("-- Count: " + allowThreadEntry.ToString() + " --");

                foreach (FrameObjectElement foE in _queueItem)
                {
                    Debug.WriteLine("Current Camera ID: " + CW.Camobject.id +
                        " : Camera ID : " + foE.cameraID.ToString() +
                        " : Camera Tag : " + foE.cameraTag.ToString() + " :");
                }

                MultipleFrameEventArgs newMul = new MultipleFrameEventArgs();
                newMul.itemObj = _queueItem;

                if (OnMultupleFrameEvent != null)
                    OnMultupleFrameEvent(newMul);

                _queueItem.Clear();
                isAllow = true;
                isClear = true;
                Debug.WriteLine("Queue item count: " + _queueItem.Count.ToString() +
                    " : isClear : " + isClear.ToString());
            }
        }   
    }
}

基本上,我在这里想要实现的目标是收集帧 ID、标签、其第一帧和第二帧,然后存储在一个对象中(struct FrameObjectElement)。每 2 个帧的集合将代表 1 个相机标签,这就是它在这里的作用的意义。然后框架对象被排入_queueObject中。接下来我会有一个条件if(allowThreadEntry == PermitEntryCount)。因此,这里所做的是每次访问此函数时,allowThreadEntry 都会递增,而 permitCountEntry 保持不变。然后此函数将继续并将 _queueObject 中的第一个元素排入 _queueItem 中,一旦满足 _queueItem 的所需计数,它将引发一个向另一个类发出信号。此类将通过引发 Camera 类之前订阅的信号来进行响应,如下所示 MainForm.OnReceiveMultipleFrameEvent += new MainForm.ReceiveMultipleFrameEvent(MainForm_OnReceiveMultipleFrameEvent)

void
MainForm_OnReceiveMultipleFrameEvent(MainForm.ReceiveMultpleFrameEventArgs
e)  {       permitEntryCount++;     }

收到信号后,permitEntryCount 将增加,从而允许再次访问该函数。我这样做的原因是因为这个类的创建取决于我有多少个对象相机。如果我有 11 个摄像头,我将有 11 个 workerThread 运行处理此类。我将它们的帧放入非静态队列中,并将它们的第一个元素收集到静态队列中,该队列将传递给我的其他进程。我在这里面临的问题如下:

=============================  Count : 1760 ===============================
Queue item count: 0 : isClear : True
Thread ID: 17 - 3 queue in QueueObject : Camera ID : 3 : Camera Tag :
3372 : Queue item count : 1
Thread ID: 24 - 6 queue in QueueObject : Camera ID :10 : Camera Tag :
4367 : Queue item count : 2
Thread ID: 23 - 5 queue in QueueObject : Camera ID : 9 : Camera Tag :
4415 : Queue item count : 3
Thread ID: 19 - 1 queue in QueueObject : Camera ID : 5 : Camera Tag :
4108 : Queue item count : 4
Thread ID: 20 - 5 queue in QueueObject : Camera ID : 6 : Camera Tag :
3768 : Queue item count : 5
Thread ID: 14 - 1 queue in QueueObject : Camera ID : 0 : Camera Tag :
2837 : Queue item count : 6
Thread ID: 21 - 1 queue in QueueObject : Camera ID : 7 : Camera Tag :
3246 : Queue item count : 7
Thread ID: 16 - 1 queue in QueueObject : Camera ID : 2 : Camera Tag :
3552 : Queue item count : 8
Thread ID: 18 - 6 queue in QueueObject : Camera ID : 4 : Camera Tag :
3117 : Queue item count : 9
Thread ID: 15 - 3 queue in QueueObject : Camera ID :1 : Camera Tag :
2315 : Queue item count : 10
Thread ID: 22 - 4 queue in QueueObject : Camera ID :8 : Camera Tag :
4853 : Queue item count : 11
Current Camera ID: 8 : Camera ID : 3 : Camera Tag : 3372 :
Current Camera ID: 8 : Camera ID :10 : Camera Tag : 4367 :
Current Camera ID: 8 : Camera ID : 9 : Camera Tag : 4415 :
Current Camera ID: 8 : Camera ID : 5 : Camera Tag : 4108 :
Current Camera ID: 8 : Camera ID : 6 : Camera Tag : 3768 :
Current Camera ID: 8 : Camera ID : 0 : Camera Tag : 2837 :
Current Camera ID: 8 : Camera ID : 7 : Camera Tag : 3246 :
Current Camera ID: 8 : Camera ID : 2 : Camera Tag : 3552 :
Current Camera ID: 8 : Camera ID : 4 : Camera Tag : 3117 :
Current Camera ID: 8 : Camera ID : 1 : Camera Tag : 2315 :
Current Camera ID: 8 : Camera ID : 8 : Camera Tag : 4853 :
=============================  Count : 1761 ===============================
Queue item count: 0 : isClear : True
Thread ID: 14 - 1 queue in QueueObject : Camera ID : 0 : Camera Tag :
2838 : Queue item count : 1
Thread ID: 16 - 1 queue in QueueObject : Camera ID : 2 : Camera Tag :
3553 : Queue item count : 2
Thread ID: 21 - 1 queue in QueueObject : Camera ID : 7 : Camera Tag :
3247 : Queue item count : 3
Thread ID: 24 - 1 queue in QueueObject : Camera ID :10 : Camera Tag :
4374 : Queue item count : 4
Thread ID: 23 - 6 queue in QueueObject : Camera ID : 9 : Camera Tag :
4416 : Queue item count : 5
Thread ID: 17 - 4 queue in QueueObject : Camera ID : 3 : Camera Tag :
3373 : Queue item count : 7
Thread ID: 15 - 3 queue in QueueObject : Camera ID : 1 : Camera Tag :
2316 : Queue item count : 7
Thread ID: 18 - 6 queue in QueueObject : Camera ID : 4 : Camera Tag :
3118 : Queue item count : 8
Thread ID: 20 - 6 queue in QueueObject : Camera ID : 6 : Camera Tag :
3769 : Queue item count : 9
Thread ID: 22 - 4 queue in QueueObject : Camera ID :8 : Camera Tag :
4854 : Queue item count : 10

我应该在 _queueItem 中有不同的计数,因为创建的每个对象只能在该段中访问一次,从而让我知道它们的元素将被排队到 _queueItem 中。但不幸的是,不知何故,程序运行一段时间后,就会出现如上所示的情况。无论我是否在这部分应用锁定 _queueItem.Enqueue(_queueObject.Peek()); 我仍然会遇到问题。我可以知道我哪里做错了吗?

最佳答案

您说队列是静态,但您已锁定实例:

lock (this)
{
    _queueItem.Enqueue(_queueObject.Peek());
}

如果您有多个实例,则意味着每个实例都独立锁定。更好的方法是拥有一个专用的静态锁定对象,并对其进行锁定。您也许可以通过以下方式作弊:

lock (_queueItem)
{
    _queueItem.Enqueue(_queueObject.Peek());
}

如果 _queueItem 从未重新分配,但最安全的方法是:

static readonly object lockObj = new object();
lock (lockObj)
{
    _queueItem.Enqueue(_queueObject.Peek());
}

请注意,所有对队列的访问必须同步,并且必须所有使用相同的锁定对象。

如果分别与两个队列通信,您可能可以减少一些争用,但在这种情况下尽量避免使用嵌套锁,因为如果做得不好,可能会导致死锁;例如,要从实例队列中查看并排入静态队列,您可以使用:

object item;
lock(instanceLock) {
    item = _queueObject.Peek();
}
lock(staticLock) {
    _queueItem.Enqueue(item);
}

另请注意,即使像 .Count 这样简单的事情也需要同步,并且最好进行双重检查(您无法在方法中尽早检查计数,然后假设仍然有东西要出列,除非您在整个持续时间内保持锁定)。您的代码重复使用 .Count - 因此请非常小心。 .Count 是暂时的:一旦你读完它,如果你放弃锁,你必须假设它已经是错误的。

关于c# - 多线程进程中静态 Queue.Enqueue 期间意外数据丢失,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13193709/

相关文章:

c# - 在设计时通过属性更改 Silverlight/WPF 中的内部用户控件

python - 线程无限期挂起

c - 我想在 C 中并行编程,我应该避免使用 pthread_join() 吗?

c - scandir 真的是线程安全的吗?

c# - 在运行时解析构造函数依赖(没有属性)

c# - 如何在<选择列表项>中插入不同的值

c++ - 线程安全的 std::cout

java - ConcurrentHashMap 的计算、computeIfAbsent 和computeIfPresent 方法是完全原子的吗?

c# - 通过 json 传递 datetime 时 API 对象为 null

c++ - 线程和 sleep