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将只有一个实例。MyImage对和Refresh()可能来自不同线程的调用。据我了解,里面的代码lock(locker)在另一个线程中完成之前不会被执行,但我仍然得到错误。任何人都可以指出代码中的缺陷吗?

异常看起来像这样:

System.Drawing.dll 中出现“System.InvalidOperationException”类型的第一次机会异常

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

在 System.Drawing.Graphics.FromImage(图像图像)

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

4

4 回答 4

10

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
        }
    }

}
于 2013-02-02T10:18:48.867 回答
5

被锁定的对象是否是静态的并不重要。问题是lock(locker)一旦返回位图,getter 方法的内部就会解锁。返回的位图引用不受锁保护,可以在调用的同时进行修改Refresh

一种可能的解决方案是锁定位图本身,但如果不小心执行,可能会导致死锁。

于 2013-02-02T10:39:52.927 回答
1

在我的应用程序中,最好的解决方案是:

  • 将带有文件的 dir 复制到另一个 tmp。目录(带有 guid 名称)
  • 每个用户使用 tmp 文件
  • 删除包含文件的 tmp 目录

在我的应用程序中有:

  • 每个请求时长 1 分钟
  • 最大用户数为 120(Intranet 应用程序)
  • 没有人愿意等待 5-10 分钟来生成报告

复制几个文件大约增加 0,01-0,2 sek。对于每个请求,最好对所有应用程序进行静态锁定,并且用户不需要等待 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();
    }
于 2014-12-18T11:01:00.347 回答
0

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

                Image newimg = (Image)img.Clone();
于 2014-02-22T19:11:45.373 回答