Wie kann ich eine Datei in C # kopieren, ohne einen Thread zu blockieren?
Wie kann ich eine Datei in C # kopieren, ohne einen Thread zu blockieren?
Antworten:
Die Idee der asynchronen Programmierung besteht darin, dem aufrufenden Thread (vorausgesetzt, es handelt sich um einen Thread-Pool-Thread) zu ermöglichen, zur Verwendung für eine andere Aufgabe zum Thread-Pool zurückzukehren, während die asynchrone E / A abgeschlossen ist. Unter der Haube wird der Anrufkontext in eine Datenstruktur gestopft, und ein oder mehrere E / A-Abschluss-Threads überwachen den Anruf, der auf den Abschluss wartet. Wenn IO abgeschlossen ist, ruft der Abschluss-Thread einen Thread-Pool auf, der den Aufrufkontext wiederherstellt. Auf diese Weise werden anstelle von 100 blockierenden Threads nur die Abschluss-Threads und einige Thread-Pool-Threads verwendet, die größtenteils im Leerlauf herumliegen.
Das Beste, was ich mir einfallen lassen kann, ist:
public async Task CopyFileAsync(string sourcePath, string destinationPath)
{
using (Stream source = File.Open(sourcePath))
{
using(Stream destination = File.Create(destinationPath))
{
await source.CopyToAsync(destination);
}
}
}
Ich habe diesbezüglich jedoch keine umfangreichen Perfektionstests durchgeführt. Ich mache mir ein wenig Sorgen, denn wenn es so einfach wäre, wäre es bereits in den Kernbibliotheken.
Warten Sie, was ich hinter den Kulissen beschreibe. Wenn Sie sich einen Überblick über die Funktionsweise verschaffen möchten, ist es wahrscheinlich hilfreich, Jeff Richters AsyncEnumerator zu verstehen. Sie sind vielleicht nicht ganz die gleiche Linie für Linie, aber die Ideen sind wirklich nah. Wenn Sie sich jemals einen Aufrufstapel mit einer "asynchronen" Methode ansehen, wird MoveNext darauf angezeigt.
Was das Verschieben betrifft, muss es nicht asynchron sein, wenn es wirklich ein "Verschieben" und keine Kopie ist, und dann löschen. Verschieben ist eine schnelle atomare Operation gegen die Dateitabelle. Dies funktioniert jedoch nur, wenn Sie nicht versuchen, die Datei auf eine andere Partition zu verschieben.
async
muss so schwierig sein wie das Bauen des Todessterns ... diese Antwort hat jetzt 2 Jahre ... und nichts hat sich geändert! Nein File.CopyAsync
, nein File.GetInfoAsync
, nein Directory.EnumerateAsync
.
Hier ist eine asynchrone Dateikopiermethode, die dem Betriebssystem Hinweise gibt, dass wir nacheinander lesen und schreiben, damit Daten beim Lesen vorab abgerufen werden können und die Dinge für das Schreiben bereit sind:
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);
}
Sie können auch mit der Puffergröße experimentieren. Hier sind es 4096 Bytes.
Ich habe den Code von @DrewNoakes leicht verbessert (Leistung und Stornierung):
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);
}
await CopyFileAsync().ConfigureAwait(false)
CopyToAsync
wird die Geschwindigkeit beim Schreiben auf eine Netzwerkfreigabe erheblich verringert. Die Verwendung der Standardeinstellung 81920 ist eine bessere Option. In meinem Fall stieg die Geschwindigkeit von 2 Mbit / s auf 25 Mbit / s. Eine Erklärung finden Sie in dieser verwandten Frage .
await sourceStream.CopyToAsync().ConfigureAwait(false)
hier korrekt, da es dem verbleibenden Methodencode (nichts) egal ist, in welchem Kontext er ausgeführt wird. Ihre aufrufende Methode verwendet eine eigene await CopyFileAsync()
mit einer eigenen ConfigureAwait()
, die festgelegt wird, true
wenn sie nicht explizit festgelegt wird.
Es gibt zwar einige Umstände, die Sie vermeiden möchten Task.Run
, aber es Task.Run(() => File.Move(source, dest)
wird funktionieren. Es ist eine Überlegung wert, denn wenn eine Datei einfach auf dieselbe Festplatte / dasselbe Volume verschoben wird, ist dies ein fast sofortiger Vorgang, da die Header geändert werden, der Dateiinhalt jedoch nicht verschoben wird. Die verschiedenen "reinen" asynchronen Methoden kopieren den Stream ausnahmslos, auch wenn dies nicht erforderlich ist, und können daher in der Praxis etwas langsamer sein.
Sie können asynchrone Delegaten verwenden
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
}
}
Sie können es nennen als:
AsyncFileCopier.AsynFileCopy("abc.txt", "xyz.txt");
Dieser Link informiert Sie über die verschiedenen Techniken der Asyn-Codierung
Sie können dies tun, wie in diesem Artikel vorgeschlagen:
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, es gibt keine asynchrone API auf hoher Ebene zum Kopieren einer Datei. Allerdings können Sie Ihre eigenen API bauen diese Aufgabe mit zu erreichen Stream.BeginRead/EndRead
und Stream.BeginWrite/EndWrite
APIs. Alternativ können Sie die BeginInvoke/EndInvoke
in den Antworten hier erwähnte Methode verwenden. Beachten Sie jedoch, dass die asynchronen E / A-Vorgänge nicht blockiert werden. Sie führen die Aufgabe lediglich in einem separaten Thread aus.
Ich würde vorschlagen, dass die in den Programmiersprachen .Net verfügbare File Copy IO-Funktion in jedem Fall asynchron ist. Nachdem ich es in meinem Programm zum Verschieben kleiner Dateien verwendet habe, scheinen nachfolgende Anweisungen ausgeführt zu werden, bevor die eigentliche Dateikopie abgeschlossen ist. Ich gehe davon aus, dass die ausführbare Datei Windows die Aufgabe gibt, die Kopie zu erstellen, und dann sofort zurückkehrt, um die nächste Anweisung auszuführen - ohne darauf zu warten, dass Windows fertig ist. Dies zwingt mich, while-Schleifen direkt nach dem Aufruf zum Kopieren zu erstellen, die ausgeführt werden, bis ich bestätigen kann, dass der Kopiervorgang abgeschlossen ist.
Der richtige Weg zum Kopieren: Verwenden Sie einen separaten Thread.
So könnten Sie es machen (synchron):
//.. [code]
doFileCopy();
// .. [more code]
So geht's asynchron:
// .. [code]
new System.Threading.Thread(doFileCopy).Start();
// .. [more code]
Dies ist eine sehr naive Art, Dinge zu tun. Gut gemacht, würde die Lösung eine Ereignis- / Delegatenmethode enthalten, um den Status der Dateikopie zu melden und wichtige Ereignisse wie Fehler, Abschluss usw. zu benachrichtigen.
Prost, jrh