Quelle est la bonne façon de copier/déplacer des fichiers de manière asynchrone en C #?
L'idée de la programmation asynchrone est de permettre au thread appelant (en supposant qu'il s'agit d'un thread de pool de threads) de retourner au pool de threads pour une utilisation sur une autre tâche pendant que async IO se termine. Sous le capot, l'appel le contexte est inséré dans une structure de données et 1 ou plusieurs IO threads d'achèvement surveillent l'appel en attente d'achèvement. Lorsque IO termine le thread d'achèvement appelle à nouveau sur un thread) pool restaure le contexte d'appel. De cette façon, au lieu de 100 threads bloquant, il n'y a que les threads d'achèvement et quelques threads de pool de threads assis autour principalement inactifs.
Le mieux que je puisse trouver est:
public async Task CopyFileAsync(string sourcePath, string destinationPath)
{
using (Stream source = File.Open(sourcePath))
{
using(Stream destination = File.Create(destinationPath))
{
await source.CopyToAsync(destination);
}
}
}
Je n'ai cependant pas fait de tests de performance approfondis à ce sujet. Je suis un peu inquiet car si c'était aussi simple, ce serait déjà dans les bibliothèques de base.
attendre fait ce que je décris dans les coulisses. Si vous voulez avoir une idée générale de son fonctionnement, il serait probablement utile de comprendre AsyncEnumerator de Jeff Richter. Ils peuvent ne pas être complètement la même ligne pour ligne mais les idées sont vraiment proches. Si vous regardez une pile d'appels à partir d'une méthode "async", vous verrez MoveNext dessus.
En ce qui concerne le déplacement, il n'a pas besoin d'être asynchrone s'il s'agit vraiment d'un "déplacement" et non d'une copie, puis de la supprimer. Move est une opération atomique rapide sur la table de fichiers. Cela ne fonctionne que de cette façon si vous n'essayez pas de déplacer le fichier vers une autre partition.
Voici une méthode de copie de fichier asynchrone qui donne au système d'exploitation des indications que nous lisons et écrivons séquentiellement, afin qu'il puisse pré-récupérer des données sur la lecture et que les choses soient prêtes pour l'écriture:
public static async Task CopyFileAsync(string sourceFile, string destinationFile)
{
using (var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan))
using (var destinationStream = new FileStream(destinationFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan))
await sourceStream.CopyToAsync(destinationStream);
}
Vous pouvez également expérimenter avec la taille du tampon. Voici 4096 octets.
J'ai légèrement amélioré le code de @DrewNoakes (performances et annulation):
public static async Task CopyFileAsync(string sourceFile, string destinationFile, CancellationToken cancellationToken)
{
var fileOptions = FileOptions.Asynchronous | FileOptions.SequentialScan;
var bufferSize = 4096;
using (var sourceStream =
new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, fileOptions))
using (var destinationStream =
new FileStream(destinationFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize, fileOptions))
await sourceStream.CopyToAsync(destinationStream, bufferSize, cancellationToken)
.ConfigureAwait(continueOnCapturedContext: false);
}
Vous pouvez utiliser des délégués asynchrones
public class AsyncFileCopier
{
public delegate void FileCopyDelegate(string sourceFile, string destFile);
public static void AsynFileCopy(string sourceFile, string destFile)
{
FileCopyDelegate del = new FileCopyDelegate(FileCopy);
IAsyncResult result = del.BeginInvoke(sourceFile, destFile, CallBackAfterFileCopied, null);
}
public static void FileCopy(string sourceFile, string destFile)
{
// Code to copy the file
}
public static void CallBackAfterFileCopied(IAsyncResult result)
{
// Code to be run after file copy is done
}
}
Vous pouvez l'appeler comme:
AsyncFileCopier.AsynFileCopy("abc.txt", "xyz.txt");
Cette lien vous indique les différentes techniques de codage asyn
Bien que vous souhaitiez éviter Task.Run
Dans certaines circonstances, Task.Run(() => File.Move(source, dest)
fonctionnera. Cela vaut la peine d'être considéré car lorsqu'un fichier est simplement déplacé sur le même disque/volume, il s'agit d'une opération presque instantanée, car les en-têtes sont modifiés mais le contenu du fichier n'est pas déplacé. Les différentes méthodes asynchrones "pures" copient invariablement le flux, même lorsque cela n'est pas nécessaire, et peuvent donc être un peu plus lentes en pratique.
Vous pouvez le faire comme this article suggéré:
public static void CopyStreamToStream(
Stream source, Stream destination,
Action<Stream, Stream, Exception> completed)
{
byte[] buffer = new byte[0x1000];
AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
Action<Exception> done = e =>
{
if(completed != null) asyncOp.Post(delegate
{
completed(source, destination, e);
}, null);
};
AsyncCallback rc = null;
rc = readResult =>
{
try
{
int read = source.EndRead(readResult);
if(read > 0)
{
destination.BeginWrite(buffer, 0, read, writeResult =>
{
try
{
destination.EndWrite(writeResult);
source.BeginRead(
buffer, 0, buffer.Length, rc, null);
}
catch(Exception exc) { done(exc); }
}, null);
}
else done(null);
}
catch(Exception exc) { done(exc); }
};
source.BeginRead(buffer, 0, buffer.Length, rc, null);
AFAIK, il n'y a pas d'API asynchrone de haut niveau pour copier un fichier. Cependant, vous pouvez créer votre propre API pour accomplir cette tâche en utilisant Stream.BeginRead/EndRead
et Stream.BeginWrite/EndWrite
API. Vous pouvez également utiliser BeginInvoke/EndInvoke
méthode comme mentionné dans les réponses ici, mais vous devez garder à l'esprit qu'ils ne seront pas des E/S asynchrones non bloquantes. Ils effectuent simplement la tâche sur un thread séparé.