5

为了创建非缓冲文件上传,我扩展了 System.Web.Http.WebHost.WebHostBufferPolicySelector,覆盖了本文所述的函数 UseBufferedInputStream():http: //www.strathweb.com/2012/09/dealing -with-large-files-in-asp-net-web-api/。当一个文件被发布到我的控制器时,我可以在跟踪输出中看到被覆盖的函数 UseBufferedInputStream() 肯定会按预期返回 FALSE。但是,使用诊断工具,我可以看到内存随着文件的上传而增长。

在我的自定义 MediaTypeFormatter 中似乎出现了大量内存使用(类似于此处的 FileMediaFormatter:http://lonetechie.com/ 。正是在这个格式化程序中,我想将传入的文件增量写入磁盘,但我还需要解析 json 并使用 Content-Type:multipart/form-data 上传进行一些其他操作。因此,我使用 HttpContent 方法 ReadAsMultiPartAsync(),这似乎是内存增长的来源。我在“等待”之前/之后放置了跟踪输出,看起来当任务阻塞时内存使用量正在迅速增加。

在 ReadAsMultiPartAsync() 返回的部分中找到文件内容后,我将使用 Stream.CopyTo() 将文件内容写入磁盘。这会按预期写入磁盘,但不幸的是,此时源文件已经在内存中。

有人对可能出现的问题有任何想法吗?似乎 ReadAsMultiPartAsync() 正在缓冲整个帖子数据;如果这是真的,为什么我们需要 var fileStream = await fileContent.ReadAsStreamAsync() 来获取文件内容?有没有另一种方法来完成部分的拆分而不将它们读入内存?我的 MediaTypeFormatter 中的代码如下所示:

// save the stream so we can seek/read again later
Stream stream = await content.ReadAsStreamAsync();  

var parts = await content.ReadAsMultipartAsync(); // <- memory usage grows rapidly

if (!content.IsMimeMultipartContent())
{
    throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);               
}

//
// pull data out of parts.Contents, process json, etc.
//

// find the file data in the multipart contents
var fileContent = parts.Contents.FirstOrDefault(
x => x.Headers.ContentDisposition.DispositionType.ToLower().Trim() == "form-data" && 
x.Headers.ContentDisposition.Name.ToLower().Trim() == "\"" + DATA_CONTENT_DISPOSITION_NAME_FILE_CONTENTS + "\"");

// write the file to disk
using (var fileStream = await fileContent.ReadAsStreamAsync())
{
    using (FileStream toDisk = File.OpenWrite("myUploadedFile.bin"))
    {
        ((Stream)fileStream).CopyTo(toDisk);
    }
}
4

1 回答 1

11

WebHostBufferPolicySelector仅指定底层请求是否无缓冲。这就是 Web API 将在后台执行的操作:

IHostBufferPolicySelector policySelector = _bufferPolicySelector.Value;
bool isInputBuffered = policySelector == null ? true : policySelector.UseBufferedInputStream(httpContextBase);
    Stream inputStream = isInputBuffered
                  ? requestBase.InputStream
          : httpContextBase.ApplicationInstance.Request.GetBufferlessInputStream();

因此,如果您的实现返回 false,则请求是无缓冲的。

但是,ReadAsMultipartAsync()将所有内容加载到MemoryStream- 因为如果您不指定提供程序,它默认为 MultipartMemoryStreamProvider。

要在处理每个部分时自动将文件保存到磁盘,请使用MultipartFormDataStreamProvider(如果您处理文件和表单数据)或MultipartFileStreamProvider(如果您只处理文件)。

在asp.net这里有一个例子。在这些示例中,一切都发生在控制器中,但没有理由不使用它,即格式化程序。

另一种选择,如果你真的想玩流,是实现一个从MultipartStreamProvider继承的自定义类,它会在获取流的一部分后立即触发你想要的任何处理。用法类似于上述提供程序 - 您需要将其传递给ReadAsMultipartAsync(provider)方法。

最后——如果你有自杀的感觉——因为理论上底层的请求流是无缓冲的,你可以在你的控制器或格式化程序中使用这样的东西:

            Stream stream = HttpContext.Current.Request.GetBufferlessInputStream();
            byte[] b = new byte[32*1024];
            while ((n = stream.Read(b, 0, b.Length)) > 0)
            {
                //do stuff with stream bit
            }

但当然,这是非常好的,因为没有更好的词,“贫民窟”。

于 2013-02-16T04:21:25.463 回答