合并图像时出现C#Threading Lock错误

时间:2010-01-26 17:39:19

标签: c# multithreading locking

我正在尝试创建一个多线程程序,从一个图片框中获取某个位图,每个线程分析并更改其中的一部分,然后将其保存回图片框。 我已经使用了一个lock()来处理共享位图对象和图片框的指令,但由于某些原因,我仍然每隔6-10次运行“对象目前在其他地方使用”错误。

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

我试着尽可能多地读取lock()方法,但它仍然有点不清楚,所以我可能会误用它。所以我的问题是,为什么锁不阻止线程执行指令?我误用了吗?或者我有可以使用的解决方法吗?

非常感谢任何帮助。

3 个答案:

答案 0 :(得分:5)

原因是变量pictureBox1与GUI线程具有亲缘关系。您无法访问它并从单独的后台线程更改它的值。要更改值,必须从与变量关联的线程中执行此操作。这通常通过.Invoke

完成

试试这个

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

即便如此,我认为你仍然有问题,因为值bmp2来自多个线程。当后台线程在其上创建图形对象时,变量pictureBox1将尝试在GUI线程上呈现此值。

答案 1 :(得分:3)

发生错误是因为您的UI线程正在使用图像(特别是设置pictureBox.Image = someImage将导致.NET框架的ImageAnimator类查看图像,看它是否应该为其设置动画(用于动画)。例如GIF图像。

与此同时,您的后台线程正在更改图像,从而导致WinForms代码抛出“对象当前正在其他地方使用”异常。

以下代码对我有用,无论我抛出多少线程都不会崩溃:

lock (locker)
{
  using (Graphics gr = Graphics.FromImage(bmp2))
  {
      gr.DrawImage(Resources.someImage, new Rectangle(0, 0, 800, 600));
      pictureBox1.Invoke(new Action(() => pictureBox1.Image = bmp2));
  }
}

拍摄,结果证明不起作用。抛出足够的线程,它会崩溃。

我怀疑这个问题与Win32绘制你的位图有关,而后台线程正在绘制它。一个(UI)线程读取,一个(后台)线程写入。这必将导致问题。

这样的多线程错误的最佳解决方法通常是停止在线程之间共享数据。而是复制数据,让每个线程都有自己的本地副本。这是一个例子:

lock (locker)
{
    using (Graphics gr = Graphics.FromImage(bmp2))
    {
        gr.DrawImage(Resources.someImage, new Rectangle(0, 0, 800, 600));
        var clone = bmp2.Clone() as Image;
        pictureBox1.Invoke(new Action(() => pictureBox1.Image = clone));
    }
}

答案 2 :(得分:1)

修复JaredPar识别的交叉线程问题。

然后将pictureBox1.Image设置为bmp2的副本。

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

希望这适合你。如果没有,你可能想要考虑整理一个说明问题的准系统项目,以便人们可以实际运行它并使用代码。线程问题在你脑子里难以做到......