我正在实施一个MVC4 + WebAPI版本的BluImp jQuery File Upload,在我初次尝试时都可以很好地工作,但是Im试图确保在下载非常大的文件(〜2GB)的同时充分利用内存。
我已阅读Filip Woj在PushStreamContent上的文章,并尽我所能实现了这一点(删除异步部分- 也许这是问题所在?)。当我运行测试并观看TaskManager时,我并没有看到明智的内存使用差异,而是试图了解响应处理方式之间的差异。
这是我的StreamContent版本:
private HttpResponseMessage DownloadContentNonChunked() { var filename = HttpContext.Current.Request["f"]; var filePath = _storageRoot + filename; if (File.Exists(filePath)) { HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK); response.Content = new StreamContent(new FileStream(filePath, FileMode.Open, FileAccess.Read)); response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = filename }; return response; } return ControllerContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, ""); }
这是我的PushStreamContent版本:
public class FileDownloadStream { private readonly string _filename; public FileDownloadStream(string filePath) { _filename = filePath; } public void WriteToStream(Stream outputStream, HttpContent content, TransportContext context) { try { var buffer = new byte[4096]; using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read)) { var length = (int)video.Length; var bytesRead = 1; while (length > 0 && bytesRead > 0) { bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length)); outputStream.Write(buffer, 0, bytesRead); length -= bytesRead; } } } catch (HttpException ex) { return; } finally { outputStream.Close(); } } } private HttpResponseMessage DownloadContentChunked() { var filename = HttpContext.Current.Request["f"]; var filePath = _storageRoot + filename; if (File.Exists(filePath)) { var fileDownload = new FileDownloadStream(filePath); var response = Request.CreateResponse(); response.Content = new PushStreamContent(fileDownload.WriteToStream, new MediaTypeHeaderValue("application/octet-stream")); response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = filename }; return response; } return ControllerContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, ""); }
我的问题是为什么两种方法的内存使用量没有太大差异?另外,我已经下载了适用于StreamContent类型的PDB,并且可以看到对缓冲区大小的引用等(请参见下文),因此我想确切地知道PushStreamContent在StreamContent之上和之外正在做什么。我已经检查了MSDN上的Type信息,但本文对解释的解释有点小!
namespace System.Net.Http { /// <summary> /// Provides HTTP content based on a stream. /// </summary> [__DynamicallyInvokable] public class StreamContent : HttpContent { private Stream content; private int bufferSize; private bool contentConsumed; private long start; private const int defaultBufferSize = 4096; /// <summary> /// Creates a new instance of the <see cref="T:System.Net.Http.StreamContent"/> class. /// </summary> /// <param name="content">The content used to initialize the <see cref="T:System.Net.Http.StreamContent"/>.</param> [__DynamicallyInvokable] [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public StreamContent(Stream content) : this(content, 4096) { }
关于这两种方法的内存使用情况,对于StreamContent和PushStreamContent,Web API不会缓冲响应。以下代码快照来自WebHostBufferPolicySelector。源代码 在这里 。
/// <summary> /// Determines whether the host should buffer the <see cref="HttpResponseMessage"/> entity body. /// </summary> /// <param name="response">The <see cref="HttpResponseMessage"/>response for which to determine /// whether host output buffering should be used for the response entity body.</param> /// <returns><c>true</c> if buffering should be used; otherwise a streamed response should be used.</returns> public virtual bool UseBufferedOutputStream(HttpResponseMessage response) { if (response == null) { throw Error.ArgumentNull("response"); } // Any HttpContent that knows its length is presumably already buffered internally. HttpContent content = response.Content; if (content != null) { long? contentLength = content.Headers.ContentLength; if (contentLength.HasValue && contentLength.Value >= 0) { return false; } // Content length is null or -1 (meaning not known). // Buffer any HttpContent except StreamContent and PushStreamContent return !(content is StreamContent || content is PushStreamContent); } return false; }
另外,PushStreamContent适用于需要将数据“推”到流中的场景,当StreamContent从流中“拉”数据时。因此,对于当前的文件下载情况,使用StreamContent应该可以。
以下示例:
// Here when the response is being written out the data is pulled from the file to the destination(network) stream response.Content = new StreamContent(File.OpenRead(filePath)); // Here we create a push stream content so that we can use XDocument.Save to push data to the destination(network) stream XDocument xDoc = XDocument.Load("Sample.xml", LoadOptions.None); PushStreamContent xDocContent = new PushStreamContent( (stream, content, context) => { // After save we close the stream to signal that we are done writing. xDoc.Save(stream); stream.Close(); }, "application/xml");