views:

339

answers:

3

I'm trying to make a multithreaded program that takes a certain bitmap from a picturebox where each thread analyzes and changes part of it and then saves it back to the picturebox. I've used a lock() for the instructions that deal with the shared bitmap object and the picturebox but for some reason i still get "Object is currently in use elsewhere" errors every 6-10 runs.

     private Object locker = new Object();

     void doThread(Bitmap bmp2) //simplified - other references not important
     {
        //some code here
        //....  
        lock (locker)
        {
            Graphics gr = Graphics.FromImage(bmp2); //this is where i get the errors, they're related to bmp2
            gr.DrawImage(bmp, new Rectangle(0, 0, 800, 600));
            gr.Dispose();

            pictureBox1.Image = bmp2;
        }
     }

     void runThreads()
     {
        Bitmap bmp2 = new Bitmap(pictureBox1.Image);

        Thread thread1 = new Thread(delegate() { doThread(bmp2); }); 
        Thread thread2 = new Thread(delegate() { doThread(bmp2); });
        Thread thread3 = new Thread(delegate() { doThread(bmp2); });
        Thread thread4 = new Thread(delegate() { doThread(bmp2); });

        thread1.Start();
        thread2.Start();
        thread3.Start();
        thread4.Start();
    }

I've tried to read as much as I could find on the lock() method but it's still a bit unclear so I might have misused it. So my question is, why isn't the lock preventing threads from executing the instructions? Have I misused it? Or is there a workaround I could use?

Any help with this is greatly appreciated.

+5  A: 

The reason why is that the variable pictureBox1 has an affinity to the GUI thread. You cannot access it and change it's value from a separate background thread. In order to change the value you must do it from the thread the variable is associated with. This is typically done via .Invoke

Try this instead

pictureBox1.Invoke((MethodInvoker)(() => pictureBox1.Image = bmp2));

Even then I think you still have issues because the value bmp2 is used from multiple threads. The variable pictureBox1 will attempt to render this value on the GUI thread while the background thread is creating a graphics object on top of it.

JaredPar
+1  A: 

Fix the cross threading issue identified by JaredPar.

Then set pictureBox1.Image to a copy of bmp2.

Image bmp2copy = bmp2.Clone();
pictureBox1.Invoke((MethodInvoker)(() => pictureBox1.Image = bmp2copy));

Hopefully that works for you. If not, you may want to think about putting together a barebones project that illustrates the issue so people can actually run it and futz with the code. Threading issus can be too difficult to do in your head...

Bryan Batchelder
I read the post wrong, it seems this works after all! thank you very much!
Bogdan
+3  A: 
Judah Himango
but not pictureBox.Image is the problem. The real problem is bmp2 which is read by all 4 threads and "drawn over" in each thread.
Bogdan
No, not really. Due to the lock and the Invoke call, Bmp2 is read by 1 thread at a time: either a background thread, or the UI thread. Does the above code work for you? It works flawlessly here.
Judah Himango
Ah, I just threw over 5000 threads at it, and finally it crashed. You're right. Something else is going on here. I will see if I can find anything.
Judah Himango
I've updated the answer to include a cloning solution which prevents sharing the image data between threads, and instead gives the UI thread its own copy of the image. Is this feasible?
Judah Himango
This seems to fix it too, hope it's for good. It's a bit harder to put more threads at the moment but I will try and post back here. Thanks very much for your time! It is feasible! I'm new to both c# and multithreading, I don't quite understand much really. This was all a day's "having fun at home" for me. Again, thanks very much!
Bogdan