【问题标题】:Disposing of HttpResponseMessage is calling disposing of request's streamHttpResponseMessage 的处理是调用请求流的处理
【发布时间】:2021-10-18 11:32:30
【问题描述】:

我有一个方法接受 Stream 参数并将其传递给服务器

public async Task<string> Execute(Stream archive)
    {
        archive.Seek(0, SeekOrigin.Begin);
        using var content = new MultipartFormDataContent();
        content.Add(new StreamContent(archive), "file1", "file1");
        var result = "";
        using (var response = await _client.PostAsync(_uri, content))
        {
            if (response.IsSuccessStatusCode)
            {
                var stringResult = await response.Content.ReadAsStringAsync();
                result = stringResult;
            }
        }
        // here archive is already disposed
        return result;
    }

现在我实现了这个方法的重试策略。 如果调用此方法的外部代码得到“”作为结果,则它会尝试再次调用此方法。 但是档案是在那一刻处理的。 我看到归档流在处理响应后立即被处理。 为什么? 如果在这个方法之后需要流参数外面怎么办?

【问题讨论】:

    标签: c# .net-core httpclient httpresponsemessage


    【解决方案1】:

    StreamContent 将处置Stream,如it's source 中所述。那将是disposed by the MultipartContent。这将在 PostAsync 中处理...通过整个链。

    解决方案是子类化 Stream 并删除 dispose 方法,如建议的here。但是您必须确保自己处理原始流! 编辑:更新。 Stream 是一个抽象类,所以如果你知道具体的流类型会更容易,例如

    public sealed class NoDisposeMemoryStream : MemoryStream
    {
        protected override void Dispose(bool disposing) { }
    }
    

    否则,您将需要编写自己的完整 Stream 包装器,请参见底部。

    另一种解决方案是在最里面的 using 块中实现重试机制,每次失败时重置archive seek origin。这可能更安全。


        public sealed class NoDisposeStream : Stream
        {
            private Stream _original;
            public NoDisposeStream(Stream original) => _original = original
                ?? throw new ArgumentNullException(nameof(original));
            public override bool CanRead => _original.CanRead;
            public override bool CanSeek => _original.CanSeek;
            public override bool CanWrite => _original.CanWrite;
            public override long Length => _original.Length;
            public override long Position { get => _original.Position; set { _original.Position = value; } }
            public override void Flush() => _original.Flush();
            public override int Read(byte[] buffer, int offset, int count) => _original.Read(buffer, offset, count);
            public override long Seek(long offset, SeekOrigin origin) => _original.Seek(offset, origin);
            public override void SetLength(long value) => _original.SetLength(value);
            public override void Write(byte[] buffer, int offset, int count) => _original.Write(buffer, offset, count);
            protected override void Dispose(bool disposing) { }
        }
    

    【讨论】:

    • 您需要一个围绕原始流的 wrapper(并且包装器的 dispose 方法为空),而不仅仅是子类 Stream 类。
    • @Evk 使用的StreamContent 构造函数需要Stream。所以被包裹的东西无论如何都必须来自Stream,不是吗?我没有完全理解你的意思。
    • 是的,但我的意思是您编写的代码不会编译(Stream 是抽象类),而且我认为通常它没有正确描述这个想法。您需要在NoDisposeStream 的构造函数中传递原始流,然后实现Stream 类的所有方法,委托给该原始流(public override bool CanRead =&gt; _original.CanRead 等)。链接答案子类特定MemoryStream,这里不一样。
    • @Evk 哦,亲爱的。是的。我实际上并没有尝试使用我链接的答案作为参考。这使它非常难看。但我会做一个。片刻。
    【解决方案2】:

    发生这种情况是因为 HttpClient PostAsync 处理您传递的内容。 https://github.com/microsoft/referencesource/blob/master/System/net/System/Net/Http/HttpClient.cs

    相关代码:

    public Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content,
                    CancellationToken cancellationToken)
    {
        HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri);
        request.Content = content;
        return SendAsync(request, cancellationToken);
    }
    

    然后 SendAsync 调用 DisposeRequestContent ,其实现如下:

    private void DisposeRequestContent(HttpRequestMessage request)
    {
       Contract.Requires(request != null);
        
       // When a request completes, HttpClient disposes the request content so the user  doesn't have to. This also
       // ensures that a HttpContent object is only sent once using HttpClient (similar to   HttpRequestMessages
       // that can also be sent only once).
       HttpContent content = request.Content;
       if (content != null)
       {
         content.Dispose();
       }
    }
    

    cmets 说出原因

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-10-17
      • 1970-01-01
      • 2023-03-18
      • 2014-06-21
      • 2014-03-09
      • 1970-01-01
      • 1970-01-01
      • 2021-10-31
      相关资源
      最近更新 更多