【问题标题】:How to correctly handle stream dispose and position when serializing and deserializing序列化和反序列化时如何正确处理流处理和位置
【发布时间】:2016-05-23 13:40:52
【问题描述】:

我有一个系统可以使用不同的序列化器(BinaryFormatterXmlSerializerJson.Net)将数据写入文件。我已将它们包装在我自己的 IStreamSerializer 接口中,并希望确保它们在我的应用程序上下文中的行为相同。这是我的一种测试方法:

[Test]
public void JsonSerializer_RoundtripsMap_Successfully()
{
    Map map = new Map(2, 4, TileType.Grass);
    IStreamSerializer serializer = new JsonSerializer(); // Json.Net

    using (var ms = new MemoryStream())
    {
        serializer.Serialize(ms, map);
        ms.Position = 0;
        Map loaded = serializer.Deserialize<Map>(ms);
        // Asserts...
    }
}

我创建了一个MemoryStream,将它序列化并尝试读回它,断言返回的对象是否相同。这适用于 BinaryFormatterXmlSerializer。但是,当我将流位置重置为零时,Json.Net 会引发异常:

System.ObjectDisposedException : The object was used after being disposed.

这是我的 JsonSerializer.Serialize 方法:

public void Serialize(Stream stream, object data)
{
    var serializer = new Newtonsoft.Json.JsonSerializer();  
    using (var textWriter = new StreamWriter(stream))
    using (var jsonWriter = new JsonTextWriter(textWriter))
    {
        serializer.Serialize(jsonWriter, data);
    }
}

我知道 Disposeusing 语句的末尾被调用,这就是我返回测试类时无法设置流位置的原因方法。

我怎样才能使它正常工作?我已经尝试了很多可能的解决方案,但它们要么最终损坏了序列化文件,要么抛出了诸如 无法读取流之类的错误>.

这会引发 JsonReaderException 并破坏物理写入的文件,但它确实通过了测试:

public void Serialize(Stream stream, object data)
{
    var serializer = new Newtonsoft.Json.JsonSerializer();
    var textWriter = new StreamWriter(stream);
    var jsonWriter = new JsonTextWriter(textWriter);
    serializer.Serialize(jsonWriter, data);
}

同样,BinaryFormatterXmlSerializer 在我的测试用例中都能正常工作。当我调用 formatter.Serialize 时,它们似乎没有处理流,但如果我以同样的方式尝试,Json.Net 就不再写入正确的数据了。 p>

注意:我的框架只能使用自定义版本的 .Net,类似于 v3.5。

【问题讨论】:

  • 您需要在使用完JSONTextWriter 并处理它之后,将StreamWriter 处理掉。AFTER此外,如果您没有处理流,则预计文件会“损坏”。 dispose 方法通常还会将内存中剩余的内容刷新到文件中
  • StreamWriter 将获得所有权并关闭Dispose 上的流。 bool leaveOpen 有一个构造函数重载。在这里查看更多信息:msdn.microsoft.com/en-us/library/gg712853(v=vs.110).aspx
  • using 语句已经做到了,对吧?但是一旦一切都关闭了,我就不能再做 stream.Position = 0 了,这就是问题所在。但我还需要关闭流以使 Json.Net 正常工作。也许这是他们的序列化程序中的一个错误。
  • StreamWriter 将在您重置 Position 之前关闭您的 MemoryStreamStreamWriter 可以在离开您的Serialize 方法时被释放,并将关闭您的MemoryStream。这可能会有所帮助:var textWriter = new StreamWriter(stream, Encoding.UTF8, 512, true))
  • leaveOpen 构造函数重载听起来和我想要的完全一样。太糟糕了,我使用的是类似于 .Net 2.0 的 .Net 版本,具有 3.5(Unity Mono)的某些功能。那个版本没有说过载。

标签: c# serialization dispose memorystream


【解决方案1】:

StreamWriter 默认情况下拥有您传入的流的所有权,因此当您处理流编写器时,它会处理您传入的流,如果您使用this constructor,您可以传入一个布尔值,告诉它不处理传入的流。

private static readonly UTF8Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier:false, throwOnInvalidBytes:true);

public void Serialize(Stream stream, object data)
{
    var serializer = new Newtonsoft.Json.JsonSerializer();  
    using (var textWriter = new StreamWriter(stream, UTF8NoBOM, bufferSize:1024, leaveOpen:true))
    using (var jsonWriter = new JsonTextWriter(textWriter))
    {
        serializer.Serialize(jsonWriter, data);
    }
}

您只需要传入旧构造函数为第二个和第三个参数传入的默认值,它们分别是不带字节顺序标记的UTF8Encoding1024

* 我使用命名参数是因为我不喜欢传入神秘的常量,使用命名参数可以让1024true 所代表的内容更加明显。


作为替代解决方案,如果您不在 .NET 4.5 或更高版本上,您可以使用如下所示的类,该类通过除 Dispose 之外的所有 Stream 命令

public class DisposeBlocker : Stream
{
    private readonly Stream _source;
    private readonly bool _blockDispose;
    private readonly bool _blockClose;

    public DisposeBlocker(Stream source, bool blockDispose = true, bool blockClose = false)
    {
        if(source == null)
            throw new ArgumentNullException(nameof(source));
        _source = source;
        _blockDispose = blockDispose;
        _blockClose = blockClose;
    }

    protected override void Dispose(bool disposing)
    {
        if (!_blockDispose && disposing)
        {
            _source.Dispose();
        }
    }

    public override void Close()
    {
        if (!_blockClose)
        {
            _source.Close();
        }
    }

    public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
    {
        return _source.CopyToAsync(destination, bufferSize, cancellationToken);
    }

    public override void Flush()
    {
        _source.Flush();
    }

    public override Task FlushAsync(CancellationToken cancellationToken)
    {
        return _source.FlushAsync(cancellationToken);
    }

    protected override WaitHandle CreateWaitHandle()
    {
        //Obsolete method, Reference Source states just return the following.
        return new ManualResetEvent(false);
    }

    public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
    {
        return _source.BeginRead(buffer, offset, count, callback, state);
    }

    public override int EndRead(IAsyncResult asyncResult)
    {
        return _source.EndRead(asyncResult);
    }

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        return _source.ReadAsync(buffer, offset, count, cancellationToken);
    }

    public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
    {
        return _source.BeginWrite(buffer, offset, count, callback, state);
    }

    public override void EndWrite(IAsyncResult asyncResult)
    {
        _source.EndWrite(asyncResult);
    }

    public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        return _source.WriteAsync(buffer, offset, count, cancellationToken);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return _source.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
         _source.SetLength(value);
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return _source.Read(buffer, offset, count);
    }

    public override int ReadByte()
    {
        return _source.ReadByte();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        _source.Write(buffer, offset, count);
    }

    public override void WriteByte(byte value)
    {
        _source.WriteByte(value);
    }

    protected override void ObjectInvariant()
    {
        //Obsolete method, nothing to override.
    }

    public override bool CanRead
    {
        get { return _source.CanRead; }
    }

    public override bool CanSeek
    {
        get { return _source.CanSeek; }
    }

    public override bool CanTimeout
    {
        get { return _source.CanTimeout; }
    }

    public override bool CanWrite
    {
        get { return _source.CanWrite; }
    }

    public override long Length
    {
        get { return _source.Length; }
    }

    public override long Position
    {
        get { return _source.Position; }
        set { _source.Position = value; }
    }

    public override int ReadTimeout
    {
        get { return _source.ReadTimeout; }
        set { _source.ReadTimeout = value; }
    }

    public override int WriteTimeout
    {
        get { return _source.WriteTimeout; }
        set { _source.WriteTimeout = value; }
    }

    public override object InitializeLifetimeService()
    {
        return _source.InitializeLifetimeService();
    }

    public override ObjRef CreateObjRef(Type requestedType)
    {
        return _source.CreateObjRef(requestedType);
    }

    public override string ToString()
    {
        return _source.ToString();
    }

    public override bool Equals(object obj)
    {
        return _source.Equals(obj);
    }

    public override int GetHashCode()
    {
        return _source.GetHashCode();
    }
}

像这样使用

public void Serialize(Stream stream, object data)
{
    var serializer = new Newtonsoft.Json.JsonSerializer();  
    using (var textWriter = new StreamWriter(new DisposeBlocker(stream)))
    using (var jsonWriter = new JsonTextWriter(textWriter))
    {
        serializer.Serialize(jsonWriter, data);
    }
}

【讨论】:

  • 如果使用 .Net 4.6,这是一个很好的答案。我个人不能使用它,因为我的框架只支持最高 .Net 3.5。
  • 构造函数是 4.5 而不是 4.6,但这仍然对你没有帮助。
  • @Xarbrough 查看我对 pre.net 4.5 选项的更新答案。您可能需要修剪一两个方法。我在 .NET 4.5 中创建了这个类,所以像 FlushAsync( 这样的东西在那里,需要删除。
【解决方案2】:

根据 cmets 我想出了这个解决方案:

public void Serialize(Stream stream, object data)
{
    var serializer = new Newtonsoft.Json.JsonSerializer();
    var streamWriter = new StreamWriter(stream);
    serializer.Serialize(streamWriter, data);
    streamWriter.Flush();
}

我发现Json.Net 可以直接使用StreamWriter。所以现在我在最后冲洗它,但让它保持打开状态。就我的单元测试和一些实际测试而言,这是可行的。

这是一个有效的解决方案,还是我必须丢弃 StreamWriter?这些类型的内存泄漏是一个问题还是可以忽略不计?

【讨论】:

  • 释放流编写器并不是什么大问题 - 不过,正确地释放 stream 本身是必要的 - 所以请确保您的调用者根据需要释放流。
  • @Luaan 如果流是MemoryStream,如果它没有被处理也没什么大不了的。处理内存流所做的所有事情都是设置一些标志,以防止您在完成后写入,并设置一些内容为空,以便它们可以尽早被 GC。话虽如此,如果我真的写了Stream(包括MemoryStream)而不丢弃它,我想我之后需要洗个澡来洗掉我会感觉到的污物:)
  • @ScottChamberlain 好吧,我有点假设内存流仅用于测试,但是当然,处理内存流没有什么意义 - 它甚至没有将缓冲区设置为 null ,实际上。
  • @Luaan 我指的是_lastReadTask被设置为空。
猜你喜欢
  • 2019-11-13
  • 2020-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多