Peut-être plus important encore à ce stade, comment copiez-vous le contenu "en continu", ce qui signifie qu'il ne copie le flux source que lorsque quelque chose consomme le flux de destination ...?
Cela renverra un Taskqui peut être poursuivi une fois terminé, comme ceci:
await input.CopyToAsync(output)// Code from here on will be run in a continuation.
Notez que selon l'endroit où l'appel CopyToAsyncest effectué, le code qui suit peut ou non continuer sur le même thread qui l'a appelé.
Celui SynchronizationContextqui a été capturé lors de l'appel awaitdéterminera sur quel thread la continuation sera exécutée.
De plus, cet appel (et il s'agit d'un détail d'implémentation susceptible d'être modifié) encode toujours les lectures et les écritures (il ne gaspille tout simplement pas un blocage des threads à la fin des E / S).
Note 1: Cette méthode vous permettra de rendre compte de l'avancement (x octets lus jusqu'à présent ...)
Note 2: Pourquoi utiliser une taille de tampon fixe et non input.Length? Parce que cette longueur peut ne pas être disponible! De la documentation :
Si une classe dérivée de Stream ne prend pas en charge la recherche, les appels à Length, SetLength, Position et Seek lèvent une exception NotSupportedException.
Notez que ce n'est pas le moyen le plus rapide de le faire. Dans l'extrait de code fourni, vous devez attendre la fin de l'écriture avant de lire un nouveau bloc. Lorsque vous effectuez la lecture et l'écriture de manière asynchrone, cette attente disparaîtra. Dans certaines situations, la copie sera deux fois plus rapide. Cependant, cela rendra le code beaucoup plus compliqué, donc si la vitesse n'est pas un problème, restez simple et utilisez cette boucle simple. Cette question sur StackOverflow contient du code qui illustre la lecture / écriture asynchrone: stackoverflow.com/questions/1540658/… Cordialement, Sebastiaan
Sebastiaan M
16
FWIW, dans mes tests, j'ai trouvé que 4096 est en fait plus rapide que 32K. Quelque chose à voir avec la façon dont le CLR alloue des morceaux sur une certaine taille. Pour cette raison, l'implémentation .NET de Stream.CopyTo utilise apparemment 4096.
Jeff
1
Si vous voulez savoir comment CopyToAsync est implémenté ou apporter des modifications comme je l'ai fait (je devais être en mesure de spécifier le nombre maximal d'octets à copier), il est disponible en tant que CopyStreamToStreamAsync dans "Samples for Parallel Programming with the .NET Framework" code.msdn .microsoft.com / ParExtSamples
Michael
1
FIY, taille de tampon optimale iz 81920octets, pas32768
Je ne vois pas beaucoup d'exemples sur le Web utilisant ces méthodes. Est-ce parce qu'ils sont assez récents ou y a-t-il des limites?
GeneS
3
C'est parce qu'ils sont nouveaux dans .NET 4.0. Stream.CopyTo () fait exactement la même chose pour la boucle que la réponse approuvée, avec quelques vérifications supplémentaires. La taille de mémoire tampon par défaut est 4096, mais il existe également une surcharge pour en spécifier une plus grande.
Michael Edenfield
9
Le flux doit être rembobiné après la copie: instream.Position = 0;
Draykos
6
En plus de rembobiner le flux d'entrée, j'ai également trouvé un besoin de rembobiner le flux de sortie: outstream.Position = 0;
JonH
32
J'utilise les méthodes d'extension suivantes. Ils ont optimisé les surcharges lorsqu'un flux est un MemoryStream.
Les questions de base qui différencient les implémentations de "CopyStream" sont:
taille du tampon de lecture
taille des écritures
Pouvons-nous utiliser plus d'un fil (écrire pendant que nous lisons).
Les réponses à ces questions se traduisent par des implémentations très différentes de CopyStream et dépendent du type de flux dont vous disposez et de ce que vous essayez d'optimiser. La «meilleure» implémentation aurait même besoin de savoir sur quel matériel spécifique les flux lisaient et écrivaient.
... ou la meilleure implémentation pourrait avoir des surcharges pour vous permettre de spécifier la taille du tampon, la taille d'écriture et si les threads sont autorisés?
MarkJ
1
Il existe en fait une façon moins lourde de faire une copie de flux. Notez cependant que cela implique que vous pouvez stocker l'intégralité du fichier en mémoire. N'essayez pas de l'utiliser si vous travaillez avec des fichiers de plusieurs centaines de mégaoctets ou plus, sans précaution.
publicstaticvoidCopyStream(Stream input,Stream output){
using (StreamReader reader =newStreamReader(input))
using (StreamWriter writer =newStreamWriter(output)){
writer.Write(reader.ReadToEnd());}}
REMARQUE: il peut également y avoir des problèmes concernant les données binaires et les encodages de caractères.
Le constructeur par défaut de StreamWriter crée un flux UTF8 sans nomenclature ( msdn.microsoft.com/en-us/library/fysy0a4b.aspx ), il n'y a donc aucun danger de problèmes de codage. Les données binaires ne devraient certainement pas être copiées de cette façon.
kͩeͣmͮpͥ
14
on pourrait facilement affirmer que le chargement de «tout le fichier en mémoire» n'est guère considéré comme «moins lourd».
Seph
je reçois une exception de mémoire pour cette raison
ColacX
Ce n'est pas un flux à diffuser. reader.ReadToEnd()met tout en RAM
Bizhan
1
.NET Framework 4 présente la nouvelle méthode "CopyTo" de l'espace de noms Stream Class of System.IO. En utilisant cette méthode, nous pouvons copier un flux vers un autre flux de classe de flux différente.
Mais le problème avec cette implémentation différente de la classe Stream peut se comporter différemment s'il n'y a rien à lire. Un flux lisant un fichier à partir d'un disque dur local se bloquera probablement jusqu'à ce que l'opération de lecture ait lu suffisamment de données du disque pour remplir le tampon et ne renvoie moins de données que si elle atteint la fin du fichier. D'un autre côté, une lecture de flux depuis le réseau peut renvoyer moins de données même s'il reste plus de données à recevoir.
Vérifiez toujours la documentation de la classe de flux spécifique que vous utilisez avant d'utiliser une solution générique.
La solution générique fonctionnera ici - la réponse de Nick est bonne. La taille du tampon est un choix arbitraire bien sûr, mais 32K semble raisonnable. Je pense que la solution de Nick est juste de ne pas fermer les flux - laissez cela au propriétaire.
Jon Skeet
0
Il peut y avoir un moyen de le faire plus efficacement, selon le type de flux avec lequel vous travaillez. Si vous pouvez convertir l'un de vos flux ou les deux en un MemoryStream, vous pouvez utiliser la méthode GetBuffer pour travailler directement avec un tableau d'octets représentant vos données. Cela vous permet d'utiliser des méthodes comme Array.CopyTo, qui résument tous les problèmes soulevés par fryguybob. Vous pouvez simplement faire confiance à .NET pour connaître la façon optimale de copier les données.
si vous voulez qu'un procdure copie un flux vers un autre que celui que nick a posté est correct mais qu'il manque la réinitialisation de position, il devrait être
publicstaticvoidCopyStream(Stream input,Stream output){byte[] buffer =newbyte[32768];longTempPos= input.Position;while(true){int read = input.Read(buffer,0, buffer.Length);if(read <=0)return;
output.Write(buffer,0, read);}
input.Position=TempPos;// or you make Position = 0 to set it at the start}
mais s'il est en cours d'exécution n'utilisant pas une procédure, vous devez utiliser le flux de mémoire
Stream output =newMemoryStream();byte[] buffer =newbyte[32768];// or you specify the size you want of your bufferlongTempPos= input.Position;while(true){int read = input.Read(buffer,0, buffer.Length);if(read <=0)return;
output.Write(buffer,0, read);}
input.Position=TempPos;// or you make Position = 0 to set it at the start
Vous ne devez pas modifier la position du flux d'entrée, car tous les flux ne permettent pas un accès aléatoire. Dans un flux réseau, par exemple, vous ne pouvez pas changer de position, seulement lire et / ou écrire.
R. Martinho Fernandes
0
Puisqu'aucune des réponses n'a couvert une manière asynchrone de copier d'un flux à un autre, voici un modèle que j'ai utilisé avec succès dans une application de redirection de port pour copier des données d'un flux réseau à un autre. Il manque une gestion des exceptions pour souligner le motif.
constint BUFFER_SIZE =4096;staticbyte[] bufferForRead =newbyte[BUFFER_SIZE];staticbyte[] bufferForWrite =newbyte[BUFFER_SIZE];staticStream sourceStream =newMemoryStream();staticStream destinationStream =newMemoryStream();staticvoidMain(string[] args){// Initial read from source stream
sourceStream.BeginRead(bufferForRead,0, BUFFER_SIZE,BeginReadCallback,null);}privatestaticvoidBeginReadCallback(IAsyncResult asyncRes){// Finish reading from source streamint bytesRead = sourceStream.EndRead(asyncRes);// Make a copy of the buffer as we'll start another read immediatelyArray.Copy(bufferForRead,0, bufferForWrite,0, bytesRead);// Write copied buffer to destination stream
destinationStream.BeginWrite(bufferForWrite,0, bytesRead,BeginWriteCallback,null);// Start the next read (looks like async recursion I guess)
sourceStream.BeginRead(bufferForRead,0, BUFFER_SIZE,BeginReadCallback,null);}privatestaticvoidBeginWriteCallback(IAsyncResult asyncRes){// Finish writing to destination stream
destinationStream.EndWrite(asyncRes);}
Certes, si la deuxième lecture se termine avant la première écriture, vous écraserez le contenu de bufferForWrite à partir de la première lecture, avant qu'il ne soit écrit.
Réponses:
À partir de .NET 4.5, il existe la
Stream.CopyToAsync
méthodeCela renverra un
Task
qui peut être poursuivi une fois terminé, comme ceci:Notez que selon l'endroit où l'appel
CopyToAsync
est effectué, le code qui suit peut ou non continuer sur le même thread qui l'a appelé.Celui
SynchronizationContext
qui a été capturé lors de l'appelawait
déterminera sur quel thread la continuation sera exécutée.De plus, cet appel (et il s'agit d'un détail d'implémentation susceptible d'être modifié) encode toujours les lectures et les écritures (il ne gaspille tout simplement pas un blocage des threads à la fin des E / S).
À partir de .NET 4.0, il y a la
Stream.CopyTo
méthodePour .NET 3.5 et versions antérieures
Il n'y a rien de prévu dans le cadre pour aider à cela; vous devez copier le contenu manuellement, comme ceci:
Note 1: Cette méthode vous permettra de rendre compte de l'avancement (x octets lus jusqu'à présent ...)
Note 2: Pourquoi utiliser une taille de tampon fixe et non
input.Length
? Parce que cette longueur peut ne pas être disponible! De la documentation :la source
81920
octets, pas32768
MemoryStream a .WriteTo (outstream);
et .NET 4.0 a .CopyTo sur un objet de flux normal.
.NET 4.0:
la source
J'utilise les méthodes d'extension suivantes. Ils ont optimisé les surcharges lorsqu'un flux est un MemoryStream.
la source
Les questions de base qui différencient les implémentations de "CopyStream" sont:
Les réponses à ces questions se traduisent par des implémentations très différentes de CopyStream et dépendent du type de flux dont vous disposez et de ce que vous essayez d'optimiser. La «meilleure» implémentation aurait même besoin de savoir sur quel matériel spécifique les flux lisaient et écrivaient.
la source
Il existe en fait une façon moins lourde de faire une copie de flux. Notez cependant que cela implique que vous pouvez stocker l'intégralité du fichier en mémoire. N'essayez pas de l'utiliser si vous travaillez avec des fichiers de plusieurs centaines de mégaoctets ou plus, sans précaution.
REMARQUE: il peut également y avoir des problèmes concernant les données binaires et les encodages de caractères.
la source
reader.ReadToEnd()
met tout en RAM.NET Framework 4 présente la nouvelle méthode "CopyTo" de l'espace de noms Stream Class of System.IO. En utilisant cette méthode, nous pouvons copier un flux vers un autre flux de classe de flux différente.
Voici un exemple pour cela.
la source
CopyToAsync()
est encouragée.Malheureusement, il n'y a pas de solution vraiment simple. Vous pouvez essayer quelque chose comme ça:
Mais le problème avec cette implémentation différente de la classe Stream peut se comporter différemment s'il n'y a rien à lire. Un flux lisant un fichier à partir d'un disque dur local se bloquera probablement jusqu'à ce que l'opération de lecture ait lu suffisamment de données du disque pour remplir le tampon et ne renvoie moins de données que si elle atteint la fin du fichier. D'un autre côté, une lecture de flux depuis le réseau peut renvoyer moins de données même s'il reste plus de données à recevoir.
Vérifiez toujours la documentation de la classe de flux spécifique que vous utilisez avant d'utiliser une solution générique.
la source
Il peut y avoir un moyen de le faire plus efficacement, selon le type de flux avec lequel vous travaillez. Si vous pouvez convertir l'un de vos flux ou les deux en un MemoryStream, vous pouvez utiliser la méthode GetBuffer pour travailler directement avec un tableau d'octets représentant vos données. Cela vous permet d'utiliser des méthodes comme Array.CopyTo, qui résument tous les problèmes soulevés par fryguybob. Vous pouvez simplement faire confiance à .NET pour connaître la façon optimale de copier les données.
la source
si vous voulez qu'un procdure copie un flux vers un autre que celui que nick a posté est correct mais qu'il manque la réinitialisation de position, il devrait être
mais s'il est en cours d'exécution n'utilisant pas une procédure, vous devez utiliser le flux de mémoire
la source
Puisqu'aucune des réponses n'a couvert une manière asynchrone de copier d'un flux à un autre, voici un modèle que j'ai utilisé avec succès dans une application de redirection de port pour copier des données d'un flux réseau à un autre. Il manque une gestion des exceptions pour souligner le motif.
la source
Pour .NET 3.5 et avant, essayez:
la source
Facile et sûr - créez un nouveau flux à partir de la source d'origine:
la source