在多线程进程中静态Queue.Enqueue期间出现意外数据丢失

时间:2012-11-02 10:46:53

标签: c# multithreading thread-safety aforge

  

private Queue _queueObject = new   队列();

     

private static Queue _queueItem = new   队列();

     

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个摄像机标签,因此它的作用在这里。然后将frameobject排入' _queueObject'。接下来我会有一个条件' if(allowThreadEntry == permitEntryCount)'。所以这里所做的就是每次访问此功能时,' allowThreadEntry'将在' permitCountEntry'保持不变。然后这个函数将继续并将' _queueObject'中的第一个元素排入队列。 to' _queueItem'并且一旦满足所需的_queueItem计数,它就会向另一个类别发出信号。这个类将通过提出一个Camera类早先已经显示的信号来响应,如图所示' MainForm.OnReceiveMultipleFrameEvent + = new MainForm.ReceiveMultipleFrameEvent(MainForm_OnReceiveMultipleFrameEvent)'。

  

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

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

  

=============================数:1760 =============== ================

     

队列项目数:0:isClear:True

     

线程ID:QueueObject中的17 - 3队列:摄像机ID:3:摄像机标记:   3372:队列项目数:1

     

线程ID:QueueObject中的24 - 6队列:摄像机ID:10:摄像机标记:   4367:队列项目数:2

     

线程ID:QueueObject中的队列号为23 - 5:摄像机ID:9:摄像机标签:   4415:队列项目数:3

     

线程ID:QueueObject中的19 - 1队列:摄像机ID:5:摄像机标记:   4108:队列项目数:4

     

线程ID:队列对象中的20 - 5队列:摄像机ID:6:摄像机标记:   3768:队列项目数:5

     

线程ID:队列对象中的14 - 1队列:摄像机ID:0:摄像机标记:   2837:队列项目数:6

     

线程ID:21 - QueueObject中的队列:摄像机ID:7:摄像机标签:   3246:队列项目数:7

     

线程ID:QueueObject中的16 - 1队列:摄像机ID:2:摄像机标记:   3552:队列项目数:8

     

线程ID:队列对象中的18 - 6队列:摄像机ID:4:摄像机标记:   3117:队列项目数:9

     

线程ID:QueueObject中的15 - 3队列:摄像机ID:1:摄像机标记:   2315:队列项目数:10

     

线程ID:22 - QueueObject中的队列:摄像机ID:8:摄像机标记:   4853:队列项目数:11

     

当前摄像机ID:8:摄像机ID:3:摄像机标签:3372:

     

当前相机ID:8:相机ID:10:相机标签:4367:

     

当前相机ID:8:相机ID:9:相机标签:4415:

     

当前相机ID:8:相机ID:5:相机标签:4108:

     

当前摄像机ID:8:摄像机ID:6:摄像机标签:3768:

     

当前摄像机ID:8:摄像机ID:0:摄像机标签:2837:

     

当前摄像机ID:8:摄像机ID:7:摄像机标签:3246:

     

当前摄像机ID:8:摄像机ID:2:摄像机标签:3552:

     

当前摄像机ID:8:摄像机ID:4:摄像机标签:3117:

     

当前相机ID:8:相机ID:1:相机标签:2315:

     

当前摄像机ID:8:摄像机ID:8:摄像机标签:4853:

     

=============================数量:1761 =============== ================

     

队列项目数:0:isClear:True

     

线程ID:队列对象中的14 - 1队列:摄像机ID:0:摄像机标记:   2838:队列项目数:1

     

线程ID:QueueObject中的16 - 1队列:摄像机ID:2:摄像机标记:   3553:队列项目数:2

     

线程ID:21 - QueueObject中的队列:摄像机ID:7:摄像机标签:   3247:队列项目数:3

     

线程ID:QueueObject中的24 - 1队列:摄像机ID:10:摄像机标记:   4374:队列项目数:4

     

线程ID:QueueObject中的23 - 6队列:摄像机ID:9:摄像机标记:   4416:队列项目数:5

     

线程ID:队列对象中的17 - 4队列:摄像机ID:3:摄像机标记:   3373:队列项目数:7

     

线程ID:QueueObject中的15 - 3队列:摄像机ID:1:摄像机标记:   2316:队列项目数:7

     

线程ID:队列对象中的18 - 6队列:摄像机ID:4:摄像机标记:   3118:队列项目数:8

     

线程ID:QueueObject中的20 - 6队列:摄像机ID:6:摄像机标记:   3769:队列项目数:9

     

线程ID:22 - QueueObject中的队列:摄像机ID:8:摄像机标记:   4854:队列项目数:10

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

4 个答案:

答案 0 :(得分:3)

您说队列为static,但您已锁定实例

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

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

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

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

另请注意,即使像.Count这样简单的事情也需要同步,并且理想情况下需要进行双重检查(您无法在方法中检查早期计数,然后再进行检查假设仍有东西出队,除非你在整个持续时间内保持锁定。您的代码重复使用.Count - 因此请非常小心。 .Count是暂时的:只要你阅读它,如果放弃锁定,就必须认为它已经错了。

答案 1 :(得分:2)

使用this锁定是邪恶的。检查几个参考文献:

Why is lock(this) {…} bad?

http://haacked.com/archive/2005/04/12/neverlockthis.aspx

始终使用像:

这样的专用对象
private static object _queueLock = new object();

答案 2 :(得分:2)

为什么不让Queue处理锁定?使用内部同步的ConcurrentQueue<T>

答案 3 :(得分:1)

请勿使用锁定(此) - 请参阅here。如果要在类的多个实例之间共享队列,则需要锁定由所有实例共享的静态对象。目前,每个类实例都有自己的锁,因此它将忽略其他实例应用的任何锁。