0

I have read a few similar questions here on SO, and through this one I understood that two threads cannot access a bitmap object at the same time, which seems logical. I didn"t find a solution, though.

I am working on a multi-threaded application involving a camera sending images to my program at a stable 10 FPS rate. My code uses several background workers to pick images from the camera feed and extract data from them. Overall, it works quite well (I started using it in actual experiments), but one of the threads sometime crashes the app because it tries to access a bitmap that is being used elsewhere (in fact it is being refreshed by its "creating" thread).

I would like to find a way to avoid this crash. Can I make sure that the bitmap is not beoing used before the thread tries to access it ? Is there another way to do ?

Before I add the code, please note that I am not a professional developer. I started coding less that 3 months ago, so my code is probably not the best that could have been done, and I will gladly listen to you suggestions. Also, the API from the camera I am using is VERY strange, and I had a very hard time getting things to work somewhat smoothly (see my other questions regarding the Vimba API). I didn't intend to use such a convoluted syntax, but it is the only one that made things work smoothly.

Here is a "stripped down" version of the code I wrote :

    /// Frame queueing and reception from the camera
    private void OnFrameReceived(Frame frame)
    {
        frame.Fill(ref BitmapFromCamera);
        mycamera.QueueFrame(frame);
        bgw1.RunWorkerAsync();
    }

    /// Handles image reception from the camera : creating all the bitmaps I will need afterward
    private void bgw1_DoWork(object s, DoWorkEventArgs e)
    {
        BMLiveCamera = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
        BMDataHist = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
        pictureBoxLiveCamera.Image = BMLiveCamera.Clone(cloneRect, BMLiveCamera.PixelFormat);
        //There are much more that are created, but only those ones are relevant here
    }

    /// Does the graphic work and the maths for plotting a live-updating histogram
    private void bgw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (this.InvokeRequired)
        {
            BeginInvoke((Action)(() => bgw1_RunWorkerCompleted(sender, e)));
        }
        else
        {
            new Thread(() =>
            {
                Thread.CurrentThread.IsBackground = true;
                DataFromBitmap DataFromBitmapHist = new DataFromBitmap(BMDataHist.Clone(cloneRect, BMDataHist.PixelFormat)); //THIS IS THE LINE THAT  CAUSES THE CODE TO CRASH
                PixelColorCount = DataFromBitmapHist.ColorCountOutput();
                //I know, creating a new thread here, after making sure we were running on the UI thread is strange, but it greatly enhanced the smoothness of the execution. Th application crashed consistantly within 10 seconds of starting before I used this syntax
            }).Start();

            chartHist.Titles["Title2"].Visible = false;
            chartHist.Series["Pixel count"].Points.Clear();

            //Plotting the pixel counter, to detect saturation
            for (int i = PixelColorCount.Length - 50; i < PixelColorCount.Length; i++)
            {
                chartHist.Series["Pixel count"].Points.AddXY(i, PixelColorCount[i]);
            }

            //If there are saturated pixels : toggle a title on chartHist to warn the user
            if (PixelColorCount.Last() > 1)
            {
                chartHist.Titles["Title1"].Visible = false;
                chartHist.Titles["Title2"].Visible = true;
            }
            else
            {
                chartHist.Titles["Title1"].Visible = true;
                chartHist.Titles["Title2"].Visible = false;
            }
        }
    }

As I said, I think the crash occurs when the DataFromBitmapclass is ran at the moment bgw1_DoWork updates the BMDataHist bitmap. The crash doesn't happen consistantly, though. Sometime it happens almost immediately after the application starts, sometime it runs smoothly for 2 hours without crashing. I already tried creating the bitmaps in the UI thred, but it makes the application much less smooth.

Anyone got an idea ? I can add more details if needed.

Thanks !

EDIT : following the comments, I have gotten rid of the BacggroundWorker, and used Tasks instead of creating Thread. As noted in the comments, OnFrameReceived runs on a separate thread Here is the updated code :

private void OnFrameReceived(Frame frame)
    {
        frame.Fill(ref BitmapFromCamera);
        mycamera.QueueFrame(frame);

        Task.Factory.StartNew(() => 
        {
            lock(lockObject)
            {
                Thread.CurrentThread.IsBackground = true;
                BMPBLiveExpClickInit = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
                BMPBLiveExpClickFinal = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
                 MDataLiveExpClickInit = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
                BMDataLiveExpClickFinal = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
                BMPBTimerTickFinal = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
                BMDataTimerTickFinal = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
                BMPBFixInit = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
                BMPBFixFinal = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
                BMDataFixInit = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
                BMDataFixFinal = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
                pictureBoxLiveCamera.Image = BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat);
                DataFromBitmap DataFromBitmapHist = new DataFromBitmap(BitmapFromCamera.Clone(cloneRect, BitmapFromCamera.PixelFormat));
                PixelColorCount = DataFromBitmapHist.ColorCountOutput();
            }
        });

        BeginInvoke((Action)(() =>
        {
            chartHist.Titles["Title2"].Visible = false;
            chartHist.Series["Pixel count"].Points.Clear();

            //Plotting the pixel counter, to detect saturation
            for (int i = PixelColorCount.Length - 50; i < PixelColorCount.Length; i++)
            {
                chartHist.Series["Pixel count"].Points.AddXY(i, PixelColorCount[i]);
            }

            //If there are saturated pixels : toggle a title on chartHist to warn the user
            if (PixelColorCount.Last() > 1)
            {
                chartHist.Titles["Title1"].Visible = false;
                chartHist.Titles["Title2"].Visible = true;
            }
            else
            {
                chartHist.Titles["Title1"].Visible = true;
                chartHist.Titles["Title2"].Visible = false;
            }
        }));
    }

It runs, but the crash still happens, more often I would even say. Moreover the UI is much less responsive, the application if overall less smooth than it was before. I guess the code is much more "correct" now, but it is also less performing. Why ? What could I do about it ?

Trion
  • 115
  • 11
  • 1
    Hmmm... `pictureBoxLiveCamera.Image =` should not be in a DoWork. – bommelding Jun 18 '18 at 09:39
  • And `this.InvokeRequired` will never be `true` in a Completed event. Unless you use the Bgw completely wrong. – bommelding Jun 18 '18 at 09:40
  • On which thread is `OnFrameReceived()` executed? – bommelding Jun 18 '18 at 09:42
  • On the thread you are refering to somebody stated that you need to synchronize the access to the bitmap using lock(bmp) { bmp.DoStuff()}. Did you try that? – StefanFFM Jun 18 '18 at 10:01
  • @trion - a few more code review points: You call RunWorkerAsync() from some other thread than the GUI thread. That is completely out of scope for the Bgw and makes it useless. – bommelding Jun 18 '18 at 10:23
  • Do not create your own threads, use Tasks or the ThreadPool. – bommelding Jun 18 '18 at 10:23
  • Cleanly separate operating on the bitmaps from the GUI. Again, that pictureBoxLiveCamera doesn't belong there. – bommelding Jun 18 '18 at 10:24
  • Thank you @bommelding, I will take your advices into consideration and rework my code ! – Trion Jun 18 '18 at 11:23
  • @bommelding About the InvokeRequired, if I don't use it the code throws a crossthread error about chartHist. I guess it means OnFrameReceived runs on a separate thread. – Trion Jun 18 '18 at 13:40
  • Yes, it does. And that means the Bgw adds no value and only obfuscates things. – bommelding Jun 18 '18 at 13:53
  • @bommelding Thanks for your help. I got rid of the Bgw and used Tasks instead of Threads. It runs, but not as good as before. I edited the post with the new code. – Trion Jun 20 '18 at 13:14
  • I'll read up later but don't do `Thread.CurrentThread.IsBackground = true;` in a Task. You borrowed a thread, don't mess with its properties. – bommelding Jun 20 '18 at 13:16
  • The usage of `PixelColorCount` doesn't look correct (thread-safe). – bommelding Jun 20 '18 at 13:19

0 Answers0