C#コンソールアプリケーションで、HTTP経由でlargeファイル(2 GB)をダウンロードする必要があります。問題は、約1.2 GB後にアプリケーションがメモリ不足になることです。
これが私が使っているコードです:
WebClient request = new WebClient();
request.Credentials = new NetworkCredential(username, password);
byte[] fileData = request.DownloadData(baseURL + fName);
ご覧のとおり...ファイルを直接メモリに読み込んでいます。チャンクでHTTPからデータを読み取り、ディスク上のファイルに書き込む場合、これを解決できると確信しています。
どうすればこれを行うことができますか?
WebClient.DownloadFile を使用すると、ファイルに直接保存できます。
WebClientクラスは、簡略化されたシナリオ用のクラスです。単純なシナリオを通り過ぎたら(そしてそれがわかったら)、少しフォールバックしてWebRequestを使用する必要があります。
WebRequestを使用すると、応答ストリームにアクセスできるようになり、完了するまでループして、少しずつ読み取り、少しずつ書き込むことができます。
例:
public void MyDownloadFile(Uri url, string outputFilePath)
{
const int BUFFER_SIZE = 16 * 1024;
using (var outputFileStream = File.Create(outputFilePath, BUFFER_SIZE))
{
var req = WebRequest.Create(url);
using (var response = req.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
var buffer = new byte[BUFFER_SIZE];
int bytesRead;
do
{
bytesRead = responseStream.Read(buffer, 0, BUFFER_SIZE);
outputFileStream.Write(buffer, 0, bytesRead);
} while (bytesRead > 0);
}
}
}
}
WebClient.DownloadFileが機能する場合は、それを最適なソリューションと呼びます。 「DownloadFile」の回答が投稿される前に、上記を書きました。私も早朝に書きすぎたので、一粒の塩(とテスト)が必要になるかもしれません。
メモリーを再利用できるようにするには、応答ストリームを取得してからブロックを読み取り、各ブロックをファイルに書き込む必要があります。
あなたがそれを書いたように、すべての応答、すべて2GBはメモリにある必要があります。単一の.NETオブジェクトの2GBの制限に達する64ビットシステムでも。
更新:より簡単なオプション。 WebClient
を取得して作業を行います。データをファイルに直接書き込む DownloadFile
メソッドを使用します。
WebClient.OpenReadはストリームを返します。Readを使用してコンテンツをループするだけなので、データはメモリにバッファリングされませんが、ブロックでファイルに書き込むことができます。
私は this のようなものを使用します
接続が中断される可能性があるため、ファイルを小さなチャンクでダウンロードすることをお勧めします。
Akkaストリームは、マルチスレッドを使用してSystem.IO.Streamから小さなチャンクでファイルをダウンロードするのに役立ちます。 https://getakka.net/articles/intro/what-is-akka.html
Downloadメソッドは、長いfileStartで始まるファイルにバイトを追加します。ファイルが存在しない場合、fileStart値は0でなければなりません。
using Akka.Actor;
using Akka.IO;
using Akka.Streams;
using Akka.Streams.Dsl;
using Akka.Streams.IO;
private static Sink<ByteString, Task<IOResult>> FileSink(string filename)
{
return Flow.Create<ByteString>()
.ToMaterialized(FileIO.ToFile(new FileInfo(filename), FileMode.Append), Keep.Right);
}
private async Task Download(string path, Uri uri, long fileStart)
{
using (var system = ActorSystem.Create("system"))
using (var materializer = system.Materializer())
{
HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
request.AddRange(fileStart);
using (WebResponse response = request.GetResponse())
{
Stream stream = response.GetResponseStream();
await StreamConverters.FromInputStream(() => stream, chunkSize: 1024)
.RunWith(FileSink(path), materializer);
}
}
}