1

希望你能帮我 :)

背景:

我正在用 C# 创建一个具有一些特定功能的相对基本的图像查看器应用程序。

一个目标是让它加载相对较大的图像(~8mb,实际上只有.jpg)足够快,以提供无延迟的浏览体验(用左右箭头键翻阅图像)。

我的解决方案:

为了实现这一点,我创建了一个带有循环缓冲区的 BufferManager 类。有一个方法 MaintainBuffer 的类,它是一个无限循环,保持缓冲区充满。它只是检查当前查看图像的左侧和右侧的缓冲区中是否至少有 K 个图像,如果没有,它会将图像加载到缓冲区中,直到存在为止。现在我很自然地在一个新线程上调用了这个 MaintainBuffer 方法,这样用户就可以翻阅图像而不会注意到 BufferManager 在后台工作,因此应用程序可以简单地向用户显示缓冲区中的图像。

我的解决方案的问题:

我的问题是 Thread2 将图像加载到缓冲区中会减慢我的 Thread1(主线程)的速度,因此每次用户翻转到新图像时,在将新图像加载到缓冲区时都会出现一个小的延迟。

我的调试尝试:

我检查了我的任务管理器,以确保线程没有在同一个核心或类似的东西上执行,但我可以看到两个不同核心上的使用率出现两个明显的峰值。这两个线程共享一些内存,其中包括缓冲区,所以我认为这可能是问题所在,并尝试使用临时变量并且只将引用移动到缓冲区中,这没有帮助(可能是优化?)。

现在我读到一些模糊地暗示 IO 操作会减慢我所有线程的东西,但这对我来说没有太大意义,因为 CPU 不应该真正参与将文件移动到主内存中,只有 Thread2 应该等待 IO 操作完成。

我的问题:

  • 假设只有 Thread2 的 IO 操作应该减慢速度,我错了吗?
  • 问题可能是我在线程之间共享数据吗?如果是这样,我该如何解决?
  • 还有什么可能导致我的麻烦?在我对多线程的理解中,我是否遗漏了一些基本的东西?
  • 甚至有比我试图做的更好的解决方案吗?

编码:

在我的 Form1 类中调用线程:

private void browser_Load(object sender, EventArgs e)
    {
        //This is just getting the image and other file filenames (others needed for a feature)
        string[] files = Directory.GetFiles(path);
        imageSets = new Dictionary<string, List<string>>();
        List<string> associatedFiles;
        foreach (string f in files)
        {
            string filename = System.IO.Path.GetFileNameWithoutExtension(f);
            string filenameExt = System.IO.Path.GetFileName(f);

            if (imageSets.ContainsKey(filename))
            {
                imageSets.TryGetValue(filename, out associatedFiles);  
                associatedFiles.Add(filenameExt); 
            }
            else
            {
                imageSets.Add(filename, new List<string>(){filenameExt});
            }
        }

        //Getting only the names of the images
        images = imageSets.Keys.ToList<string>();

        //Creating a new bufferManager, note that the 'images' list is used by the object for getting the names of the images to load
        bufferManager = new BufferManager(ref images, path);

        //Have the bufferManager load the first image and put it in the buffer
        currentImage = bufferManager.Init();

        //Create a thread for maintaining the buffer
        bufferThread = new Thread(bufferManager.MaintainBuffer);

        bufferThread.Start();

        //Display the intial image to the user
        img_preview.BackgroundImage = currentImage;
    }

BufferManager的初始化方法:

public Image Init()
    {
        currentIndex = lastBufferIndex = firstBufferIndex = 0;

        Image fullSize = ImageTool.FromFile(path + "\\" + images[0] + ".jpg");
        //Resize the image in the buffer to save memory and display faster
        imageBuffer[0] = ImageTool.Resize(fullSize, 460); //The buffer is a simple array of fixed size
        fullSize.Dispose();
        return imageBuffer[0];
    }

这里有趣的方法:在 Thread2 上调用的 MaintainBuffer

public void MaintainBuffer()
    {
        if (images.Count <= totalBufferSize)
        {
            //If the buffer can hold all the images without swapping images ind out, just load them all and dont worry about maintaining the buffer
            totalBufferSize = images.Count;

            for (int i = 0; i < totalBufferSize; ++i)
            {
                imageBuffer[i] = ImageTool.Resize(ImageTool.FromFile(path + "\\" + images[i] + ".jpg"), 460);
            }

            return;
        }


        while (!killBuffer)
        {
                //Ensure that enough images are buffered to the left of the current image. This looks a bit advanced due to being a circulair buffer, but the idea is simple
                while (currentBufferIndex >= firstBufferIndex ? (currentBufferIndex - firstBufferIndex) < bufferWidth : (currentBufferIndex + totalBufferSize - firstBufferIndex) < bufferWidth)
                {
                    --firstBufferIndex;
                    if (firstBufferIndex < 0)
                        firstBufferIndex = totalBufferSize - 1;

                    if (imageBuffer[firstBufferIndex] != null)
                        imageBuffer[firstBufferIndex].Dispose();    //Release the memory used by the image that is now "overwritten"

                    //Getting the image index of the image to load next
                    int imageNameIndex = currentIndex - (currentBufferIndex >= firstBufferIndex ? currentBufferIndex - firstBufferIndex : currentBufferIndex + totalBufferSize - firstBufferIndex);
                    if (imageNameIndex < 0)
                        imageNameIndex = images.Count + imageNameIndex;

                    Image fullSize = ImageTool.FromFile(path + "\\" + images[imageNameIndex] + ".jpg");
                    imageBuffer[firstBufferIndex] = ImageTool.Resize(fullSize, 460);
                     fullSize.Dispose();
                }

                //Ensure that enough images are buffered to the right of the current image
                while (currentBufferIndex <= lastBufferIndex ? (lastBufferIndex - currentBufferIndex) < bufferWidth : (lastBufferIndex + totalBufferSize - currentBufferIndex) < bufferWidth)
                {
                    ++lastBufferIndex;
                    if (lastBufferIndex > totalBufferSize - 1)
                        lastBufferIndex = 0;

                    if (imageBuffer[lastBufferIndex] != null)
                        imageBuffer[lastBufferIndex].Dispose();

                    int imageNameIndex = (currentIndex + (currentBufferIndex <= lastBufferIndex ? lastBufferIndex - currentBufferIndex : lastBufferIndex + totalBufferSize - currentBufferIndex)) % (images.Count);


                    Image fullSize = ImageTool.FromFile(path + "\\" + images[imageNameIndex] + ".jpg");
                    imageBuffer[lastBufferIndex] = ImageTool.Resize(fullSize, 460);
                   fullSize.Dispose();
                }

            Thread.Sleep(100);
        }

    }

我省略了左右翻转图像的代码,因为它非常简单。但是,它确实会更新 Thread1 上 BufferManager 对象的 currentIndex(正在查看的图像的索引)和 currentBufferIndex(循环缓冲区中的当前图像位置),这是可能导致问题的数据共享示例?

正如您可能注意到的那样,我对多线程的经验并不多,所以我希望您能给我一些指示!谢谢

4

0 回答 0