私は MVC4 + BluImp jQuery File UploadのWebAPIバージョン を実装していますが、すべてが最初の試みでうまくいきますが、非常に大きなファイル(〜2GB)をダウンロードしながらメモリを最大限に活用しようとしています。
私は PushStreamContentに関するFilip Wojの記事 を読み、可能な限り実装しました(非同期部分を削除します-これが問題なのでしょうか?)。 Imがテストを実行してTaskManagerを見ると、メモリ使用量の違いはあまり見られず、Imは応答の処理方法の違いを理解しようとします。
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, "");
}
私の質問は、なぜ2つのアプローチの間でメモリ使用量に大きな違いが見られないのですか?さらに、StreamContentタイプのPDBをダウンロードし、バッファーサイズなどへの参照を確認できるため(下記参照)、PushStreamContentがStreamContentを超えて何をしているのかを正確に知りたいと思います。私はMSDNでタイプ情報を確認しましたが、この記事では説明が少しわかりませんでした。
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からのものです。ソースコード here .
/// <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");