对象当前正在其他地方使用

5

我遇到了这个错误,看起来是因为同一个Bitmap对象被不同的线程访问。但是我已经在使用锁了。

public class MySingleInstanceClass
{
    private Object locker = new Object();

    private Bitmap myImage = new Bitmap(100, 100);

    public Bitmap MyImage
    {
        get
        {
            lock (locker)
                return myImage;
        }
        private set
        {
            lock (locker)
                myImage = value;
        }
    }

    private void Refresh()
    {
        lock (locker)
        {
            var g = Graphics.FromImage(myImage);
            // do more processing
        }
    }
}

MySingleInstanceClass 只会有一个实例。调用 MyImageRefresh() 的线程可能不同。据我所知,lock(locker) 内的代码在另一个线程中完成之前不会执行,但我仍然遇到了错误。有人能指出代码中的缺陷吗?

异常看起来像这样:

类型为“System.InvalidOperationException”的一次首要机会异常发生在 System.Drawing.dll 中

错误: 对象当前正在被其他地方使用。

在 System.Drawing.Graphics.FromImage(Image image) 处

在(指向包含 var g = Graphics.FromImage(myImage); 的行)


getter/setter 内部的锁似乎毫无意义... - Mitch Wheat
你认为通过锁定MyImage的getter(和私有setter?)可以实现什么目的?我只是在胡思乱想,所以不要认为我已经有解决方案了,但如果锁定行为在使用此类的类中,这样做是否更有意义呢? - bas
1
@MitchWheat 嗯,我正在确保调用 MyImage 的线程等待,直到调用 Refresh() 的线程完成那里的代码块,否则我会在处理完成之前返回 myImage。您觉得我不需要它吗?为什么? - Georgii Oleinikov
如果有内部异常,您能展示异常消息和内部异常吗? - Hamlet Hakobyan
@HamletHakobyan,基本上就是问题标题中的内容;我把它添加在底部了。 - Georgii Oleinikov
4个回答

11

locker对象不是静态的,因此每个新实例都会创建自己的locker;如果使用多个对象,则需要将locker创建为静态,以防止其他线程访问。

private static Object locker = new Object();

对于单个对象情况,使用非静态类级别变量作为锁定器是合适的。如果您正在使用此情况,我认为Singleton的实现存在一些问题。

更新:

public sealed class MySingleInstanceClass
{
    private static volatile MySingleInstanceClass instance;
    private static object syncRoot = new Object();
    private Bitmap myImage;

    private MySingleInstanceClass() 
    {
        myImage = new Bitmap(100, 100);
    }

    public static MySingleInstanceClass Instance
    {
        get
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    if (instance == null)
                        instance = new MySingleInstanceClass();
                }
            }

            return instance;
        }
    }  

    public Bitmap MyImage
    {
        get
        {
            lock (syncRoot)
                return myImage;
        }
        private set
        {
            lock (syncRoot)
                myImage = value;
        }
    }

    public void Refresh()
    {
        lock (syncRoot)
        {
            var g = Graphics.FromImage(myImage);
            // do more processing
        }
    }

}

只有一个实例,因此只有一个锁(该类是单例)。 - Georgii Oleinikov
那么您提供的代码与问题不匹配? - daryal
@MitchWheat 你能解释一下为什么非静态对象不能用于锁定对实例成员的访问吗? - Hamlet Hakobyan
@daryal 实现单例模式修复这个错误,我采用什么方式真的很重要吗? - Georgii Oleinikov
@daryal 你能解释一下如果我从singletonmulti-instance情况下的syncRoot对象的声明中删除static关键字会发生什么吗? - Hamlet Hakobyan
显示剩余2条评论

5
无论被锁定的对象是静态的还是非静态的,都没有关系。问题在于getter方法内的lock(locker)会在位图返回后立即解锁。位图的返回引用没有受到锁的保护,在调用Refresh时可能同时被修改。
一个可能的解决方案是对位图本身进行锁定,但如果不小心进行,这可能会导致死锁。

如果在getter中返回位图的副本会怎样? - Georgii Oleinikov
如果你将 return myImage 替换为 return (Bitmap)myImage.Clone(),那么你也可以避免这个错误。但是如果没有看到使用 MySingleInstanceClass 的代码,很难说这是否是一个好的解决方法。 - Dirk
看起来我不能直接添加评论到另一个提供的解决方案MySingleInstanceClass,所以我在这里写。想象一下有2个线程。线程1只是重复调用MySingleInstanceClass.Instance.Refresh()。线程2调用MySingleInstanceClass.Instance.MyImage然后修改返回的位图。这将导致你在问题中展示的异常。 - Dirk

1
在我的应用程序中,最好的解决方案是:
  • 将带有文件的目录复制到另一个临时目录(使用GUID名称)
  • 每个用户使用临时文件
  • 删除包含文件的临时目录
在我的应用程序中:
  • 每个请求需要1分钟
  • 最多可以有120个用户(内部网络应用程序)
  • 没有人想等待5-10分钟来生成报告
复制几个文件会为每个请求增加约0.01-0.2秒。相对于整个应用程序的静态锁定,这更好,用户不需要等待10分钟才能生成报告(10个用户同时点击生成按钮)。
        private void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
    {
        // Get the subdirectories for the specified directory.
        DirectoryInfo dir = new DirectoryInfo(sourceDirName);
        DirectoryInfo[] dirs = dir.GetDirectories();

        if (!dir.Exists)
        {
            throw new DirectoryNotFoundException(
                "Source directory does not exist or could not be found: "
                + sourceDirName);
        }

        // If the destination directory doesn't exist, create it. 
        if (!Directory.Exists(destDirName))
        {
            Directory.CreateDirectory(destDirName);
        }

        // Get the files in the directory and copy them to the new location.
        FileInfo[] files = dir.GetFiles();
        foreach (FileInfo file in files)
        {
            string temppath = Path.Combine(destDirName, file.Name);
            file.CopyTo(temppath, false);
        }

        // If copying subdirectories, copy them and their contents to new location. 
        if (copySubDirs)
        {
            foreach (DirectoryInfo subdir in dirs)
            {
                string temppath = Path.Combine(destDirName, subdir.Name);
                DirectoryCopy(subdir.FullName, temppath, copySubDirs);
            }
        }
    }


        private void DeleteReportExecutionDirectory(string dirPath)
    {
        System.IO.DirectoryInfo downloadedMessageInfo = new DirectoryInfo(dirPath);
        foreach (FileInfo file in downloadedMessageInfo.GetFiles())
        {
            file.Delete();
        }
        foreach (DirectoryInfo dir in downloadedMessageInfo.GetDirectories())
        {
            dir.Delete(true);
        }
        downloadedMessageInfo.Delete();
    }

0

在发送图像到方法之前,您可以克隆该图像

                Image newimg = (Image)img.Clone();

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