J'ai besoin de télécharger un fichier large (2 Go) via HTTP dans une application console C #. Le problème est que, après environ 1,2 Go, l'application manque de mémoire.
Voici le code que j'utilise:
WebClient request = new WebClient();
request.Credentials = new NetworkCredential(username, password);
byte[] fileData = request.DownloadData(baseURL + fName);
Comme vous pouvez le voir ... Je lis le fichier directement dans la mémoire. Je suis à peu près sûr de pouvoir résoudre ce problème si je lisais les données HTTP en morceaux et les écrivais dans un fichier sur disque.
Comment pourrais-je faire ça?
Si vous utilisez WebClient.DownloadFile vous pouvez l’enregistrer directement dans un fichier.
La classe WebClient est celle des scénarios simplifiés. Une fois que vous avez dépassé des scénarios simples (et ce que vous avez fait), vous devrez vous replier un peu et utiliser WebRequest.
Avec WebRequest, vous aurez accès au flux de réponses et pourrez le lire, lire et écrire un peu, jusqu'à ce que vous ayez terminé.
Exemple:
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);
}
}
}
}
Notez que si WebClient.DownloadFile fonctionne, je l'appellerais la meilleure solution. J'ai écrit ce qui précède avant que la réponse "DownloadFile" ne soit publiée. Je l'ai aussi écrit bien trop tôt le matin, alors un grain de sel (et des tests) peut être nécessaire.
Vous devez obtenir le flux de réponse, puis lire des blocs, en écrivant chaque bloc dans un fichier pour permettre à la mémoire d'être réutilisée.
Comme vous l'avez écrit, l'ensemble de la réponse, tous les 2 Go, doit être en mémoire. Même sur un système 64 bits qui atteindra la limite de 2 Go pour un seul objet .NET.
Mise à jour: option plus facile. Demandez à WebClient
de faire le travail pour vous: avec sa méthode DownloadFile
qui placera les données directement dans un fichier.
WebClient.OpenRead renvoie un flux. Utilisez simplement Read pour parcourir le contenu. Ainsi, les données ne sont pas mises en mémoire tampon mais peuvent être écrites par blocs dans un fichier.
je voudrais utiliser quelque chose comme ceci
La connexion peut être interrompue, il est donc préférable de télécharger le fichier par petits morceaux.
Les flux Akka peuvent vous aider à télécharger un fichier par petits morceaux depuis un System.IO.Stream à l'aide de la lecture multiple. https://getakka.net/articles/intro/what-is-akka.html
La méthode Download ajoutera les octets au fichier en commençant par long fileStart. Si le fichier n'existe pas, la valeur fileStart doit être 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);
}
}
}