web-dev-qa-db-ja.com

.NETで(HTTPを介して)大きなファイルをダウンロードするにはどうすればよいですか?

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からデータを読み取り、ディスク上のファイルに書き込む場合、これを解決できると確信しています。

どうすればこれを行うことができますか?

24
Nick Cartwright

WebClient.DownloadFile を使用すると、ファイルに直接保存できます。

38
Alex Peck

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」の回答が投稿される前に、上記を書きました。私も早朝に書きすぎたので、一粒の塩(とテスト)が必要になるかもしれません。

34
John Saunders

メモリーを再利用できるようにするには、応答ストリームを取得してからブロックを読み取り、各ブロックをファイルに書き込む必要があります。

あなたがそれを書いたように、すべての応答、すべて2GBはメモリにある必要があります。単一の.NETオブジェクトの2GBの制限に達する64ビットシステムでも。


更新:より簡単なオプション。 WebClientを取得して作業を行います。データをファイルに直接書き込む DownloadFile メソッドを使用します。

9
Richard

WebClient.OpenReadはストリームを返します。Readを使用してコンテンツをループするだけなので、データはメモリにバッファリングされませんが、ブロックでファイルに書き込むことができます。

3
Whuppa

私は this のようなものを使用します

2
Sadegh

接続が中断される可能性があるため、ファイルを小さなチャンクでダウンロードすることをお勧めします。

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);
       }
    }
}
0
qqus