从GTK#线程访问System.Drawing.Bitmap会抛出“对象当前在其他地方使用”的异常。

6
我正在尝试使用system.drawing在GTK#中操作图像。我希望用户更新文本框后,UI能够立即更新屏幕上的图像。为了实现这一点,我尝试使用来自winforms的background worker,它可以工作,但是当文本框以更高的速度更新时,应用程序会卡住而没有错误。
因此,我查看了GTK中的多线程,这里有一个链接:http://www.mono-project.com/docs/gui/gtksharp/responsive-applications/,并创建了一个线程。
void textboxchanged()
{
    Thread thr = new Thread (new ThreadStart (ThreadRoutine));
    thr.Start ();

  }

  static void ThreadRoutine ()
  {
        LargeComputation ();

  }

  static void LargeComputation ()
  {
    image=new Bitmap(backupimage);
   //Long image processing 
  }

当输入框中的速度稍微快一点时,后台工作线程抛出“当前对象在其他地方正在使用”错误,这比后台工作线程会产生不良影响。image=new Bitmap(backupimage); 我做错了什么?

更新1:

我没有使用两个不同的线程同时处理相同的图像进行2个不同的操作。我在旧线程完成之前调用执行相同操作的线程。由于在后台工作器中,我需要一种方法来检查旧线程是否已经完成工作,然后再启动新线程。所以,我要寻找的基本上是一种检查相同线程实例是否正在运行的方法。在winforms中,我通常会这样做:if(backgroundworker.isbusy==false) then do stuff

更新2

带有性能下降的解决方案

如@voo所建议的,替换全局位图可以解决问题。我所做的是,而不是使用全局位图,我创建了一个全局字符串(文件名)。现在我使用img=new Bitmap(filename)。尝试以最快的速度执行,没有出现错误。因此,为了更新GUI,我使用了在这里建议的调用mono-project.com/docs/gui/gtksharp/responsive-applications/的方法。问题在于没有错误出现并且图像更新了,但是当打字操作足够快时,存在等待。性能有所下降。这在后台工作器中不是这种情况。是否有一种方法可以提高性能。

在大型图像处理操作方法的末尾,我添加了以下内容以更新GUI

Gtk.Application.Invoke (delegate {

            MemoryStream istream=new MemoryStream();
            img.Save (istream, System.Drawing.Imaging.ImageFormat.Png);
            istream.Position = 0;
            workimagepixbuff = new Gdk.Pixbuf (istream);
            image1.Pixbuf = workimagepixbuff.ScaleSimple (400, 300, Gdk.InterpType.Bilinear);

        });
        // cannot directly convert Bitmap to Pixbuff,so doing this 

你能给我们提供确切的异常吗?我刚刚在libgdiplus和mono源代码中进行了grep搜索,但是没有找到任何与你提供的文本相关的引用。或者你是在Windows上运行吗? - fog
@fog 我正在使用Windows系统。异常信息是“Object currently in use elsewhere”,明天我会更新具体细节,现在我无法访问电脑。我认为这是System.InvalidOperationException。 - techno
1
你是否从多个线程访问同一张图片? - Yuval Itzchakov
@YuvalItzchakov 不是,当线程运行时,它是唯一使用该图像的东西。 - techno
那么实际上交换背景图像的代码在哪里?您在GUI代码中没有显示,但是它在哪里?您如何避免创建两个后台线程并且两者都在处理相同的全局变量? - Voo
显示剩余12条评论
1个回答

3
这里的问题在于您同时在两个地方(两个线程)处理图像,而在.Net中(即GDI),图像操作不允许这样做。由于您未提供太多信息,我只是猜测。
当使用GDI操作位图图像时,背后会有BitmapData需要锁定和解锁。这种机制只是让图片在内存中可读/可写。但据我所知,当您锁定已经被锁定的BitmapData时,您会得到类似的异常:System.InvalidOperationException,“位图区域已锁定”。
对我来说,听起来像是您正在遇到这种错误,但是使用其他单词,因为您没有显式地锁定位图数据位。GDI只告诉您:“我必须锁定位图数据位,但是由于对象已在其他地方使用(已锁定),因此我无法这样做。”
解决方法可能是尝试在每次可能发生位锁定的线程之间同步位图使用情况。因此,您可能需要使用锁定关键字或类似的机制:
因此,请尝试类似以下内容的内容:
private static object _imageLock = new object();
static void LargeComputation ()
  {
     lock(_imageLock)
     {
       image=new Bitmap(backupimage);
       //Long image processing ...
     }
  }

  static void AnotherImageOperationSomewhereElse()
  {
       lock(_imageLock)
       {
          //Another image processing on backupImage or something derived from it...
       }
  }

感谢您的回答。我不是在同时使用两个不同操作的2个不同线程处理相同的图像。我正在调用执行相同操作的线程,而旧线程尚未完成。就像在后台工作器中,我需要一种方法来检查旧线程是否已完成工作,然后再启动新线程。因此,基本上我要找的是一种检查同一线程实例是否正在运行的方法。 - techno
你似乎在说“新”线程正在运行,而“旧”线程尚未完成。如果是这样,你可能正在两个不同的线程中处理图像。顺便说一下,如果你不想在“新”和“旧”线程之间并行处理(线程),你可能不想在这里使用线程。然而,如果你尝试锁定位图处理部分,在你的“旧”和“新”线程中,我的解决方案应该可以防止引发异常。有趣的是,因为我今天正在开发的项目上也遇到了同样的异常,并通过使用锁定关键字来同步线程来解决它;-) - John-Philip
我不明白这个锁定机制是如何工作的。图像锁定到底是什么,lock(imagelock)是做什么的?你看到我的更新#2了吗? - techno
@techno,我看到你的更新了,我相信你在这方面没有走对路。我无法在此为您详细解释锁定机制,但在 msdn 上简单搜索将为您提供所需的一切。基本上,您应该知道图像是在内存中位图化的,修改此内存需要“锁定”相应的内存位置。由于这个要求,Gdi 图像在“开箱即用”的情况下不是线程安全的:您必须处理更多的线程可能尝试锁定并且如果已经锁定则失败的事实。这里来了锁定关键字。 - John-Philip
锁定关键字将会阻止(暂停执行)任何尝试进入 lock {} 区块的线程,而另一个线程已经在运行它。因此,它是线程之间的同步机制,并且这正是你在此处需要来防止多个位图内存“锁定”。 - John-Philip
谢谢。我理解你告诉我的概念。但在我的情况下,问题已经通过更新#2解决了。但是感谢您提供有关同步的详细信息。我将授予您一半的赏金,因为您是SO上的新用户。 - techno

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接