在尝试创建非缓冲文件上传时,我扩展了System.Web.Http.WebHost.WebHostBufferPolicySelector,覆盖了本文所述的函数UseBufferedInputStream():http://www.strathweb.com/2012/09/dealing-with-large-files-in-asp-net-web-api/。当一个文件被POST到我的控制器时,我可以在跟踪输出中看到被覆盖的函数UseBufferedInputStream()肯定会按预期返回FALSE。但是,使用诊断工具,我可以看到内存随着文件上传而增长。
大量内存使用似乎发生在我的自定义MediaTypeFormatter中(类似于FileMediaFormatter:http://lonetechie.com/)。在这个格式化程序中我想逐步将传入的文件写入磁盘,但我还需要解析json并使用Content-Type:multipart / form-data upload执行其他操作。因此我使用HttpContent方法ReadAsMultiPartAsync(),这似乎是内存增长的来源。我已经在“await”之前/之后放置了跟踪输出,并且看起来当任务阻塞时,内存使用量会相当快地增加。
一旦我在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);
}
}
答案 0 :(得分: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或here上有一个示例。在这些例子中,一切都发生在控制器中,但没有理由不在格式化器中使用它。
另一个选择,如果你真的想要使用流,那就是实现一个继承自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
}
但当然,由于缺乏更好的词语,“贫民窟”。