4

我有一个自定义列表控件,它显示带有图像缩略图的项目。每个列表项都给出了文件的完整路径,并使用 FileStream.BeginRead 异步读取,当文件读取完成时需要使列表控件失效。

也可以随时清除列表中的项目并重新填充不同的项目。这会在需要优雅处理文件流处理的每个项目上调用 Dispose(它可能仍处于异步读取的中间)。

我将展示我正在使用的代码。我不确定在这种情况下调用和锁定对象的正确用法,在这种情况下,异步加载新文件的请求可能会在另一个文件处于异步加载过程中时出现。

public string FileName { get; set; }
public Image Image { get; set; }
public Control Parent { get; set; }

private FileStream currentFileStream;
private byte[] buffer;
private object locker = new object();
private bool loading;
private bool disposed;

public void LoadImage(string fileName)
{
    FileName = fileName;

    lock (locker)
    {           
        currentFileStream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read);
        buffer = new byte[currentFileStream.Length];
        currentFileStream.BeginRead(buffer, 0, buffer.Length, FileReadComplete, currentFileStream);

        loading = true;
    }
}

private void FileReadComplete(IAsyncResult ar)
{
    FileStream fileStreamComplete = (FileStream)ar.AsyncState;

    lock (locker)
    {
        fileStreamComplete.EndRead(ar);

        // If the finished FileStream is the more recent one requested
        // And this item has not been disposed
        if (fileStreamComplete == currentFileStream && !disposed)
        {
            try
            {
                loading = false;

                Image = new Bitmap(currentFileStream);

                currentFileStream.Close();
                currentFileStream.Dispose();

                Parent.Invalidate();
            }
            catch (Exception e)
            {
            }
            finally
            {
                currentFileStream = null;
            }
        }
        else
        {
            fileStreamComplete.Close();
            fileStreamComplete.Dispose();
        }
    }
}

protected override void Dispose(bool disposing)
{
    lock (locker)
    {
        base.Dispose(disposing);

        if (!disposed)
        {
            if (disposing)
            {
                if (Image != null)
                    Image.Dispose();
            }

            disposed = true;
        }
    }
}

编辑:从 Dispose() 方法中删除了 currentFileStream 的处理。

编辑#2:从 LoadImage() 函数中删除了 currentFileStream 的处理。它可能不应该存在,因为文件读取可能正在进行中并且在操作期间无法关闭。无论何时调用 FileReadComplete 回调,它都会被释放。

4

1 回答 1

1

我不确定我是否遵循了您的代码,但这是我推荐的。

  1. 如果您打算将其用于异步只读,请使用不同的 FileStream 构造函数。虽然它可能与您的问题无关,但只是一种更好的方法

    
    currentFileStream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read, 1024 * 8, true);
    

  2. 您不想提供位图构造函数(或 Image.FromFile)的路径并让它加载文件的任何原因?请记住,当将大量文件加载到内存中时,顺序加载它们可能会更快(如果文件驻留在像硬盘这样的顺序访问技术上)

假设您仍然想异步加载它,我会简单地将该功能封装在一个类中以实现“整洁”

您似乎正在从已读入缓冲区的同一流中加载图像。我确信这是一个问题。以下是我对您的代码的改编。主要变化是

  1. 我找不到 currentFileStream 变量的用途
  2. dispose 变量是可变的,因为它可以从多个线程访问
  3. 在 FileStream.Close 之后对 FileStream.Dispose 的调用是多余的,因此将它们删除

我还没有尝试过代码,所以你必须告诉我它是否有效

class ImageLoader : IDisposable {
    public string FileName { get; set; }
    public Image Image { get; set; }
    public Control Parent { get; set; }

    private FileStream currentFileStream;
    private byte[] buffer;
    private object locker = new object();
    Control parent;

    private volatile bool dispose = false;

    public ImageLoader(Control parent, string fileName) {
        Parent = parent;
        FileName = fileName;
        Image = null;

        currentFileStream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read, 1024 * 8, true);
        buffer = new byte[currentFileStream.Length];
        currentFileStream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(FileReadComplete), null);
    }

    private void FileReadComplete(IAsyncResult ar) {
        lock (locker) {
            try { currentFileStream.EndRead(ar); } catch (ObjectDisposedException) { }

            if (!dispose) {
                using (MemoryStream ms = new MemoryStream(buffer))
                    Image = new Bitmap(ms);
                Parent.Invalidate();
            }

            try { currentFileStream.Close(); } catch(IOException) { }
        }
    }

    public void Dispose() {
        lock (locker) {
            if (dispose)
                return;
            dispose = true;
            try { currentFileStream.Close(); } catch(IOException) { }
            if (Image != null)
                Image.Dispose();
        }
    }
}

EDIT1:根据您的评论,我在此处添加回复,因为系统不允许我在此处添加更多文本

  1. 将这样的功能封装到一个类(一组函数)或一个函数中具有良好的一致性,并且很多时候这种良好做法都会导致性能下降。您应该根据自己的情况使用它。
  2. 我不认为这是流的工作方式,但是对于小文件,我可以看到情况如何。为了解释这一点,如果您指示文件缓冲区大小为 8K,则无法将大于 8K 的文件缓存在内存中。因此,如果不进行真正的 I/O,流就无法读取整个文件(忘记 Windows 为此在屏幕后面做了什么)。此外,位图构造函数可能会执行同步 I/O,并且在您以异步模式打开它时可能会出现问题。MSDN 明确指出,在流对象的整个生命周期中,流只能以一种模式(同步或异步)使用。我坚信您应该关闭流并从您已经创建的自己的缓冲区中读取。我想说的是,它现在对你有用并不意味着它会在不同的场景中工作;你可能只是“
  3. 我同意 volatile 关键字,我只是这样练习,因为有时在更改代码时(比如我要一起摆脱锁)它可以让我避免犯错
于 2012-07-18T20:14:36.740 回答