C# FileStream释放时注意事项

前言

前一段时间,接手同事做的一个工具,该工具已经很长时间没有修改过了,前几天有反馈说,工具运行最近有点小问题.便对代码调试跟了一下,看看是哪里的问题.这不是重点,在调试的时候发现记录日志是抛异常的.

先看看这一块有没有问题:

C# FileStream释放时注意事项

c#文件流释放异常

测试代码:

private static void Main(string[] args)
{
    try
    {
        Log.WriteLog("123.txt", "hello csharp");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}
C# FileStream释放时注意事项

调用记录日志方法,出现异常,提示文件已关闭

说一下,调用文件流的Dispose方法,是没有问题的,那为什么会出现异常呢?是调用StreamWriter.Close方法时报的异常.这里应该这样写.

public static void WriteLog(string fileName, string log)
{
    string filePath = Path.Combine(Directory.GetCurrentDirectory(), fileName);
    FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);
    StreamWriter sw = new StreamWriter(fs);
    try
    {
        sw.BaseStream.Seek(0, SeekOrigin.End);
        sw.WriteLine(log);
        sw.Flush();
    }
    finally
    {
        sw.Close();    //先对StreamWriter进行关闭
        fs.Dispose();  //再对FileStream进行释放
    }
}

接着我们看看StreamWriter的Close源码:

public override void Close()
{
    Dispose(true);   //调用StreamWriter的Dispose
    GC.SuppressFinalize(this);
}

protected override void Dispose(bool disposing)
{
    try
    {
        // We need to flush any buffered data if we are being closed/disposed.
        // Also, we never close the handles for stdout & friends.  So we can safely
        // write any buffered data to those streams even during finalization, which
        // is generally the right thing to do.
        if (!_disposed && disposing)
        {
            // Note: flush on the underlying stream can throw (ex., low disk space)
            CheckAsyncTaskInProgress();
            Flush(flushStream: true, flushEncoder: true);  //调用Flush将缓冲区的数据写入到文件流中,如果先调用FileStream的Dispose进行释放,在这里提示文件关闭
        }
    }
    finally
    {
        CloseStreamFromDispose(disposing);
    }
}

private void CloseStreamFromDispose(bool disposing)
{
    // Dispose of our resources if this StreamWriter is closable.
    if (_closable && !_disposed)
    {
        try
        {
            // Attempt to close the stream even if there was an IO error from Flushing.
            // Note that Stream.Close() can potentially throw here (may or may not be
            // due to the same Flush error). In this case, we still need to ensure
            // cleaning up internal resources, hence the finally block.
            if (disposing)
            {
                _stream.Close();  //将缓冲区数据写入到文件流中,会对文件流进行关闭,
                //_stream就是在实例化StreamWriter传入的FileStream 
            }
        }
        finally
        {
            _disposed = true;
            _charLen = 0;
            base.Dispose(disposing);
        }
    }
}

接着看Flush方法源码:

private void Flush(bool flushStream, bool flushEncoder)
{
    // flushEncoder should be true at the end of the file and if
    // the user explicitly calls Flush (though not if AutoFlush is true).
    // This is required to flush any dangling characters from our UTF-7
    // and UTF-8 encoders.
    ThrowIfDisposed();      //判断文件流是否关闭,如果关闭抛出异常

    // Perf boost for Flush on non-dirty writers.
    if (_charPos == 0 && !flushStream && !flushEncoder)
    {
        return;
    }

    if (!_haveWrittenPreamble)
    {
        _haveWrittenPreamble = true;
        ReadOnlySpan preamble = _encoding.Preamble;
        if (preamble.Length > 0)
        {
            _stream.Write(preamble);
        }
    }

    // For sufficiently small char data being flushed, try to encode to the stack.
    // For anything else, fall back to allocating the byte[] buffer.
    scoped Span byteBuffer;
    if (_byteBuffer is not null)
    {
        byteBuffer = _byteBuffer;
    }
    else
    {
        int maxBytesForCharPos = _encoding.GetMaxByteCount(_charPos);
        byteBuffer = maxBytesForCharPos <= 1024 ? // arbitrary threshold
            stackalloc byte[1024] :
            (_byteBuffer = new byte[_encoding.GetMaxByteCount(_charBuffer.Length)]);
    }

    int count = _encoder.GetBytes(new ReadOnlySpan(_charBuffer, 0, _charPos), byteBuffer, flushEncoder);
    _charPos = 0;
    if (count > 0)
    {
        _stream.Write(byteBuffer.Slice(0, count));
    }

    if (flushStream)
    {
        _stream.Flush();
    }
}

图1中代码运行抛出异常,主要是对FileStream释放的时机问题.可以使用简化代码:

public static void WriteLog2(string fileName, string log)
{
    string filePath = Path.Combine(Directory.GetCurrentDirectory(), fileName);
    using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))
    {
        using (StreamWriter sw = new StreamWriter(fs))
        {
            sw.BaseStream.Seek(0, SeekOrigin.End);
            sw.WriteLine(log);
            sw.Flush();
        }
    }
}

其实在很早之前在文章中也提到过文件释放, 具体可以看看: 写更好的CSharp代码

个人能力有限,如果您发现有什么不对,请私信我

如果您觉得对您有用的话,可以点个赞或者加个关注,欢迎大家一起进行技术交流

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章