Saving an image using FileStream sometimes results in corrupt files

2.3k views Asked by At

I have an application where by I take a picture and press a save button and save the image to the computer. This works fine the majority of the time. But I am seeing some of my users with corrupt images. These images are about the right number of bytes in size but when trying to the bytes for the image it is just a load of null values.

My code for saving the photos is:

try
{
    using (FileStream stream = new FileStream(localCopyFileLocation, FileMode.Create))
    {
        JpegBitmapEncoder encoder = new JpegBitmapEncoder();
        encoder.QualityLevel = quality;
        encoder.Frames.Add(BitmapFrame.Create(photo, null, metadata, null));
        encoder.Save(stream);
    }
}
catch (Exception ex)
{
    //Write to log etc.
}

From FileStream Constructor (SafeFileHandle, FileAccess) I read this:

When Close is called, the handle is also closed and the file's handle count is decremented.

FileStream assumes that it has exclusive control over the handle. Reading, writing, or seeking while a FileStream is also holding a handle could result in data corruption. For data safety, call Flush before using the handle, and avoid calling any methods other than Close after you are done using the handle.

But I am unsure what this means? Could my users be putting their tablets to sleep while writing the file? What other reasons could it be that this is happening?

1

There are 1 answers

4
Jason Evans On BEST ANSWER

To clarify - you need to call Flush().

Calling Close() is not enough. Close() calls Dispose() which does the following

protected override void Dispose(bool disposing)
{
    try
    {
        if (this._handle != null && !this._handle.IsClosed && this._writePos > 0)
        {
            this.FlushWrite(!disposing);
        }
    }
    finally
    {
        if (this._handle != null && !this._handle.IsClosed)
        {
            this._handle.Dispose();
        }
        this._canRead = false;
        this._canWrite = false;
        this._canSeek = false;
        base.Dispose(disposing);
    }
}

On the other hand, here is what Flush() does:

public override void Flush()
{
    this.Flush(false);
}

Which calls:

public virtual void Flush(bool flushToDisk)
{
    if (this._handle.IsClosed)
    {
        __Error.FileNotOpen();
    }
    this.FlushInternalBuffer();
    if (flushToDisk && this.CanWrite)
    {
        this.FlushOSBuffer();
    }
}

The key (I think, but I'm not certain) is FlushOSBuffer()

private void FlushOSBuffer()
{
    if (!Win32Native.FlushFileBuffers(this._handle))
    {
        __Error.WinIOError();
    }
}

If calling Flush() does not work, than try the override Flush(true) which will result in the Win32 API FlushFileBuffers being called, which will flush intermediate file buffers.