适用于异步FileStream读取的结构

时间:2012-07-12 17:23:38

标签: c# asynchronous locking filestream

我有一个自定义列表控件,可以显示包含图像缩略图的项目。每个列表项都给出了文件的完整路径,并使用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回调,它都会被处理掉。

1 个答案:

答案 0 :(得分:1)

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

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

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

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

  3. 假设您仍然想要异步加载它,我只是将该功能封装在一个“neatness”的类中

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

    1. 我没有找到currentFileStream变量的用法
    2. dispose变量是易失性的,因为它可以从多个线程访问
    3. 在FileStream.Close之后调用FileStream.Dispose是多余的,因此将其删除
    4. 我没有尝试过代码,所以你必须告诉我它是否有效

      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在屏幕后面做了什么)。此外,Bitmap构造函数可能会执行同步I / O,并且在异步模式下打开它时可能会出现问题。 MSDN明确指出,流应仅在整个流对象生命周期中以一种模式(同步或异步)使用。我坚信你应该关闭流并从你已经创建的缓冲区中读取。我认为它的方式是,它现在适用于你并不意味着它将在不同的情况下工作;你可能只是“幸运”。
      3. 我同意volatile关键字,我只是这样练习,因为有时候在更改代码时(比如说我一起摆脱锁定)它可以让我免于犯错误