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

EDIT : 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. Bitmap コンストラクター (または Image.FromFile) へのパスを提供してファイルをロードさせたくない理由はありますか? 多数のファイルをメモリにロードする場合、それらを順次ロードする方が高速になる可能性があることに注意してください (ファイルがハードディスクなどの順次アクセス技術に存在する場合)。

それでも非同期でロードしたいと仮定すると、その機能を「きちんとした」クラスにカプセル化するだけです。

既にバッファに読み込んでいるのと同じストリームからイメージをロードしているようです。それは問題だと思います。以下は、あなたのコードの私の適応です。主な変更点は

  1. currentFileStream 変数の使用法が見つかりません
  2. dispose 変数は、複数のスレッドからアクセスできるため、揮発性になります。
  3. FileStream.Dispose の呼び出しは、FileStream.Close の後で冗長になるため、削除しました

コードを試していないので、動作するかどうか教えてください

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 では、ストリーム オブジェクトの有効期間中、ストリームは 1 つのモード (同期または非同期) でのみ使用する必要があると明確に述べています。ストリームを閉じて、既に作成した独自のバッファーから読み取る必要があると強く信じています。私が言いたいのは、それが今あなたのために働くということは、それが別のシナリオで働くという意味ではありません。あなたはただのかもしれません」
  3. volatile キーワードについては同意します。コードを変更するときに (たとえば、ロックをまとめて取り除くとします)、間違いから救われることがあるので、そのように練習するだけです。
于 2012-07-18T20:14:36.740 に答える