HttpResponseMessage 的处理是调用请求流的处理

Posted

技术标签:

【中文标题】HttpResponseMessage 的处理是调用请求流的处理【英文标题】:Disposing of HttpResponseMessage is calling disposing of request's stream 【发布时间】: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;
    

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

【问题讨论】:

【参考方案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 说出原因

【讨论】:

以上是关于HttpResponseMessage 的处理是调用请求流的处理的主要内容,如果未能解决你的问题,请参考以下文章

HttpResponseMessage 的处理是调用请求流的处理

在调用ReadAsStreamAsync时何时或何时调用HttpResponseMessage?

异步任务<HttpResponseMessage> 获取 VS HttpResponseMessage 获取

Web APi之Web Host消息处理管道

如果 Task<HttpResponseMessage>.IsCompleted 为真,为啥 Task<HttpResponseMessage>.Result 会抛出异常?

vue中给的默认值是调接口取到的