Posso mostrare l'avanzamento della copia dei file utilizzando FileInfo.CopyTo () in .NET?

StackOverflow https://stackoverflow.com/questions/187768

  •  06-07-2019
  •  | 
  •  

Domanda

Ho creato un'utilità di copia in c # (.NET 2.0 Framework) che copia file, directory e sottodirectory ricorsive ecc. Il programma ha una GUI che mostra il file corrente da copiare, il numero del file corrente (sequenza), il numero totale di file da copiare e la percentuale completata per le operazioni di copia. C'è anche una barra di avanzamento, che si basa sul file corrente / file totali.

Il mio problema è legato alla copia di file di grandi dimensioni. Non sono stato in grado di trovare un modo per indicare l'avanzamento della copia totale di un file di grandi dimensioni (utilizzando la mia struttura di classe corrente che utilizza il metodo FileInfo.CopyTo). Per ovviare al problema, ho separato le operazioni di copia dei file e la visualizzazione della GUI nei loro thread e ho impostato un segnale visivo per mostrare che il lavoro è in corso. Almeno l'utente è consapevole che il programma non è bloccato e sta ancora copiando i file.

Sarebbe più bello poter mostrare l'avanzamento in base al numero totale di byte o avere qualche tipo di evento generato dal metodo FileInfo.CopyTo che indica il numero totale di byte copiati dal file corrente.

Sono a conoscenza della proprietà FileInfo.Length, quindi sono sicuro che c'è un modo in cui MacGuyver il mio evento si basa su questo e ho un gestore sul lato GUI delle cose che legge gli aggiornamenti (forse basato sul controllo la proprietà FileInfo.Length dell'oggetto di destinazione usando un tipo di timer?).

Qualcuno sa un modo per farlo che sto trascurando. Se posso evitarlo, preferirei non riscrivere la mia classe per copiare byte attraverso un flusso e tracciarlo in quel modo (anche se sto pensando che potrei essere bloccato nel percorrere quella strada).

Grazie in anticipo

PS: per ora sono bloccato con il framework .NET 2.0, quindi qualsiasi soluzione che richiede funzionalità disponibili in > = 3.0 non è un'opzione per me.

PPS - Sono aperto a soluzioni in qualsiasi varietà di linguaggio .NET, non solo c #.

È stato utile?

Soluzione

FileInfo.CopyTo è sostanzialmente un wrapper attorno alla chiamata API Win32 " CopyFile " nel kernel32.dll. Questo metodo non supporta la richiamata progress.

Tuttavia, il metodo CopyFileEx funziona e puoi scrivere il tuo wrapper .NET al suo interno in pochi minuti, come descritto qui: http://www.pinvoke.net/default.aspx/kernel32.CopyFileEx

Altri suggerimenti

Ho anche utilizzato l'implementazione fornita nella risposta contrassegnata . Tuttavia, ho quindi creato un wrapper per fornire un & # 8482 più bello; API da utilizzare da .NET.

Utilizzo:

XCopy.Copy(networkFile.FullPath, temporaryFilename, true, true, (o, pce) => 
{
    worker.ReportProgress(pce.ProgressPercentage, networkFile);
});

Implementazione

/// <summary>
/// PInvoke wrapper for CopyEx
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363852.aspx
/// </summary>
public class XCopy
{
    public static void Copy(string source, string destination, bool overwrite, bool nobuffering)
    {
         new XCopy().CopyInternal(source, destination, overwrite, nobuffering, null);            
    }

    public static void Copy(string source, string destination, bool overwrite, bool nobuffering, EventHandler<ProgressChangedEventArgs> handler)
    {            
         new XCopy().CopyInternal(source, destination, overwrite, nobuffering, handler);            
    }

    private event EventHandler Completed;
    private event EventHandler<ProgressChangedEventArgs> ProgressChanged;

    private int IsCancelled;
    private int FilePercentCompleted;
    private string Source;
    private string Destination;        

    private XCopy()
    {
        IsCancelled = 0;
    }

    private void CopyInternal(string source, string destination, bool overwrite, bool nobuffering, EventHandler<ProgressChangedEventArgs> handler)
    {
        try
        {
            CopyFileFlags copyFileFlags = CopyFileFlags.COPY_FILE_RESTARTABLE;
            if (!overwrite)
                copyFileFlags |= CopyFileFlags.COPY_FILE_FAIL_IF_EXISTS;

            if (nobuffering)
                copyFileFlags |= CopyFileFlags.COPY_FILE_NO_BUFFERING;

            Source = source;
            Destination = destination;

            if (handler != null)
                ProgressChanged += handler;

            bool result = CopyFileEx(Source, Destination, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, ref IsCancelled, copyFileFlags);
            if (!result)
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        catch (Exception)
        {
            if (handler != null)
                ProgressChanged -= handler;

            throw;
        }
    }

    private void OnProgressChanged(double percent)
    {
        // only raise an event when progress has changed
        if ((int)percent > FilePercentCompleted)
        {
            FilePercentCompleted = (int)percent;

            var handler = ProgressChanged;
            if (handler != null)
                handler(this, new ProgressChangedEventArgs((int)FilePercentCompleted, null));
        }
    }

    private void OnCompleted()
    {
        var handler = Completed;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }

    #region PInvoke

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel, CopyFileFlags dwCopyFlags);

    private delegate CopyProgressResult CopyProgressRoutine(long TotalFileSize, long TotalBytesTransferred, long StreamSize, long StreamBytesTransferred, uint dwStreamNumber, CopyProgressCallbackReason dwCallbackReason,
                                                    IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);

    private enum CopyProgressResult : uint
    {
        PROGRESS_CONTINUE = 0,
        PROGRESS_CANCEL = 1,
        PROGRESS_STOP = 2,
        PROGRESS_QUIET = 3
    }

    private enum CopyProgressCallbackReason : uint
    {
        CALLBACK_CHUNK_FINISHED = 0x00000000,
        CALLBACK_STREAM_SWITCH = 0x00000001
    }

    [Flags]
    private enum CopyFileFlags : uint
    {
        COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
        COPY_FILE_NO_BUFFERING = 0x00001000,
        COPY_FILE_RESTARTABLE = 0x00000002,
        COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
        COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
    }

    private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber,
                                                   CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
    {
        if (reason == CopyProgressCallbackReason.CALLBACK_CHUNK_FINISHED)
            OnProgressChanged((transferred / (double)total) * 100.0);

        if (transferred >= total)
            OnCompleted();

        return CopyProgressResult.PROGRESS_CONTINUE;
    }

    #endregion

}

So di essere un po 'in ritardo alla festa, ma ho creato un wrapper per CopyFileEx che restituisce un Task e accetta un CancellationToken e IProgress<double>. Sfortunatamente non funzionerà nel framework .NET 2.0, ma per chiunque usi 4.5, questo ti permette di usare la await parola chiave.

public static class FileEx
{
    public static Task CopyAsync(string sourceFileName, string destFileName)
    {
        return CopyAsync(sourceFileName, destFileName, CancellationToken.None);
    }

    public static Task CopyAsync(string sourceFileName, string destFileName, CancellationToken token)
    {
        return CopyAsync(sourceFileName, destFileName, token, null);
    }

    public static Task CopyAsync(string sourceFileName, string destFileName, IProgress<double> progress)
    {
        return CopyAsync(sourceFileName, destFileName, CancellationToken.None, progress);
    }

    public static Task CopyAsync(string sourceFileName, string destFileName, CancellationToken token, IProgress<double> progress)
    {
        int pbCancel = 0;
        CopyProgressRoutine copyProgressHandler;
        if (progress != null)
        {
            copyProgressHandler = (total, transferred, streamSize, streamByteTrans, dwStreamNumber, reason, hSourceFile, hDestinationFile, lpData) =>
            {
                progress.Report((double)transferred / total * 100);
                return CopyProgressResult.PROGRESS_CONTINUE;
            };
        }
        else
        {
            copyProgressHandler = EmptyCopyProgressHandler;
        }
        token.ThrowIfCancellationRequested();
        var ctr = token.Register(() => pbCancel = 1);
        var copyTask = Task.Run(() =>
        {
            try
            {
                CopyFileEx(sourceFileName, destFileName, copyProgressHandler, IntPtr.Zero, ref pbCancel, CopyFileFlags.COPY_FILE_RESTARTABLE);
                token.ThrowIfCancellationRequested();
            }
            finally
            {
                ctr.Dispose();
            }
        }, token);
        return copyTask;
    }

    private static CopyProgressResult EmptyCopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
    {
        return CopyProgressResult.PROGRESS_CONTINUE;
    }

    #region DLL Import

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName,
       CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel,
       CopyFileFlags dwCopyFlags);

    delegate CopyProgressResult CopyProgressRoutine(
        long totalFileSize,
        long totalBytesTransferred,
        long streamSize,
        long streamBytesTransferred,
        uint dwStreamNumber,
        CopyProgressCallbackReason dwCallbackReason,
        IntPtr hSourceFile,
        IntPtr hDestinationFile,
        IntPtr lpData);

    enum CopyProgressResult : uint
    {
        PROGRESS_CONTINUE = 0,
        PROGRESS_CANCEL = 1,
        PROGRESS_STOP = 2,
        PROGRESS_QUIET = 3
    }

    enum CopyProgressCallbackReason : uint
    {
        CALLBACK_CHUNK_FINISHED = 0x00000000,
        CALLBACK_STREAM_SWITCH = 0x00000001
    }

    [Flags]
    enum CopyFileFlags : uint
    {
        COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
        COPY_FILE_RESTARTABLE = 0x00000002,
        COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
        COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
    }

    #endregion
}

Per amore di Dio non implementare la tua copia di file usando i flussi! La chiamata API Win32 CopyFile citata da Gaspar è in grado di sfruttare ad es. DMA, mentre scommetterei dollari per le ciambelle che il codice scritto da Will non sarebbe & Quot; smart & Quot; abbastanza per farlo.

CopyFileEx ti tratterà nel modo giusto, oppure potresti implementare un BackgroundWorker che controlla le dimensioni crescenti del file di destinazione e aggiorna una barra di avanzamento utilizzando tali informazioni. Il secondo metodo consente di risparmiare un PInvoke, ma il primo è probabilmente un po 'più pulito nel lungo periodo.

Per questo genere di cose sono tornato a Shell32 (o è ShellUI? Non lo so più). Questo ti dà una finestra di dialogo nativa di Windows che gli utenti sono abituati a vedere per le operazioni di copia. Immagino che sostituirebbe la tua finestra di dialogo già esistente, quindi potrebbe non essere la risposta giusta per te, ma è utile ricordare per quelle & Quot; in un pizzico & Quot; scenari.

Microsoft.VisualBasic.FileIO.FileSystem.CopyFile(
    srcPath, 
    dstPath, 
    Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,    
    Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException
);

Sì, è necessario fare riferimento all'assembly Microsoft.VisualBasic. Sono cresciuto fino a love questa assemblea.

Grazie a @Gasper e @Dennis per aver segnalato il metodo CopyFileEx. Ho esteso la risposta di Dennis con l'interruzione della copia

    /// <summary>
    /// Type indicates how the copy gets completed.
    /// </summary>
    internal enum CopyCompletedType
    {
        Succeeded,
        Aborted,
        Exception
    }

/// <summary>
/// Event arguments for file copy 
/// </summary>
internal class FileCopyEventArgs : EventArgs
{
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="type">type of the copy completed type enum</param>
    /// <param name="exception">exception if any</param>
    public FileCopyEventArgs(CopyCompletedType type, Exception exception)
    {
        Type = type;
        Exception = exception;
    }

    /// <summary>
    /// Type of the copy completed type
    /// </summary>
    public CopyCompletedType Type
    {
        get;
        private set;

    }

    /// <summary>
    /// Exception if any happend during copy.
    /// </summary>
    public Exception Exception
    {
        get;
        private set;
    }

}

/// <summary>
/// PInvoke wrapper for CopyEx
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363852.aspx
/// </summary>
internal class XCopy
{

    private int IsCancelled;
    private int FilePercentCompleted;

    public XCopy()
    {
        IsCancelled = 0;
    }

    /// <summary>
    /// Copies the file asynchronously
    /// </summary>
    /// <param name="source">the source path</param>
    /// <param name="destination">the destination path</param>
    /// <param name="nobuffering">Bufferig status</param>
    /// <param name="handler">Event handler to do file copy.</param>
    public void CopyAsync(string source, string destination, bool nobuffering)
    {
        try
        {
            //since we needed an async copy ..
            Action action = new Action(
                () => CopyInternal(source, destination, nobuffering)
                    );
            Task task = new Task(action);
            task.Start();
        }
        catch (AggregateException ex)
        {
            //handle the inner exception since exception thrown from task are wrapped in
            //aggreate exception.
            OnCompleted(CopyCompletedType.Exception, ex.InnerException);
        }
        catch (Exception ex)
        {
            OnCompleted(CopyCompletedType.Exception, ex);
        }
    }

    /// <summary>
    /// Event which will notify the subscribers if the copy gets completed
    /// There are three scenarios in which completed event will be thrown when
    /// 1.Copy succeeded
    /// 2.Copy aborted.
    /// 3.Any exception occured.
    /// These information can be obtained from the Event args.
    /// </summary>
    public event EventHandler<FileCopyEventArgs> Completed;
    /// <summary>
    /// Event which will notify the subscribers if there is any progress change while copying.
    /// This will indicate the progress percentage in its event args.
    /// </summary>
    public event EventHandler<ProgressChangedEventArgs> ProgressChanged;

    /// <summary>
    /// Aborts the copy asynchronously and throws Completed event when done.
    /// User may not want to wait for completed event in case of Abort since 
    /// the event will tell that copy has been aborted.
    /// </summary>
    public void AbortCopyAsync()
    {
        Trace.WriteLine("Aborting the copy");
        //setting this will cancel an operation since we pass the
        //reference to copyfileex and it will periodically check for this.
        //otherwise also We can check for iscancelled on onprogresschanged and return 
        //Progress_cancelled .
        IsCancelled = 1;

        Action completedEvent = new Action(() =>
            {
                //wait for some time because we ll not know when IsCancelled is set , at what time windows stops copying.
                //so after sometime this may become valid .
                Thread.Sleep(500);
                //do we need to wait for some time and send completed event.
                OnCompleted(CopyCompletedType.Aborted);
                //reset the value , otherwise if we try to copy again since value is 1 , 
                //it thinks that its aborted and wont allow to copy.
                IsCancelled = 0;
            });

        Task completedTask = new Task(completedEvent);
        completedTask.Start();
    }


    /// <summary>
    /// Copies the file using asynchronos task
    /// </summary>
    /// <param name="source">the source path</param>
    /// <param name="destination">the destination path</param>
    /// <param name="nobuffering">Buffering status</param>
    /// <param name="handler">Delegate to handle Progress changed</param>
    private void CopyInternal(string source, string destination, bool nobuffering)
    {
        CopyFileFlags copyFileFlags = CopyFileFlags.COPY_FILE_RESTARTABLE;

        if (nobuffering)
        {
            copyFileFlags |= CopyFileFlags.COPY_FILE_NO_BUFFERING;
        }

        try
        {
            Trace.WriteLine("File copy started with Source: " + source + " and destination: " + destination);
            //call win32 api.
            bool result = CopyFileEx(source, destination, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, ref IsCancelled, copyFileFlags);
            if (!result)
            {
                //when ever we get the result as false it means some error occured so get the last win 32 error.
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        catch (Exception ex)
        {
            //the mesage will contain the requested operation was aborted when the file copy
            //was cancelled. so we explicitly check for that and do a graceful exit
            if (ex.Message.Contains("aborted"))
            {
                Trace.WriteLine("Copy aborted.");
            }
            else
            {
                OnCompleted(CopyCompletedType.Exception, ex.InnerException);
            }
        }
    }

    private void OnProgressChanged(double percent)
    {
        // only raise an event when progress has changed
        if ((int)percent > FilePercentCompleted)
        {
            FilePercentCompleted = (int)percent;

            var handler = ProgressChanged;
            if (handler != null)
            {
                handler(this, new ProgressChangedEventArgs((int)FilePercentCompleted, null));
            }
        }
    }

    private void OnCompleted(CopyCompletedType type, Exception exception = null)
    {
        var handler = Completed;
        if (handler != null)
        {
            handler(this, new FileCopyEventArgs(type, exception));
        }
    }

    #region PInvoke

    /// <summary>
    /// Delegate which will be called by Win32 API for progress change
    /// </summary>
    /// <param name="total">the total size</param>
    /// <param name="transferred">the transferrred size</param>
    /// <param name="streamSize">size of the stream</param>
    /// <param name="streamByteTrans"></param>
    /// <param name="dwStreamNumber">stream number</param>
    /// <param name="reason">reason for callback</param>
    /// <param name="hSourceFile">the source file handle</param>
    /// <param name="hDestinationFile">the destination file handle</param>
    /// <param name="lpData">data passed by users</param>
    /// <returns>indicating whether to continue or do somthing else.</returns>
    private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber,
                                                   CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
    {
        //when a chunk is finished call the progress changed.
        if (reason == CopyProgressCallbackReason.CALLBACK_CHUNK_FINISHED)
        {
            OnProgressChanged((transferred / (double)total) * 100.0);
        }

        //transfer completed
        if (transferred >= total)
        {
            if (CloseHandle(hDestinationFile))
            {
                OnCompleted(CopyCompletedType.Succeeded, null);
            }
            else
            {
                OnCompleted(CopyCompletedType.Exception,
                    new System.IO.IOException("Unable to close the file handle"));
            }
        }

        return CopyProgressResult.PROGRESS_CONTINUE;
    }
    [System.Runtime.InteropServices.DllImport("Kernel32")]
    private extern static Boolean CloseHandle(IntPtr handle);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel, CopyFileFlags dwCopyFlags);

    private delegate CopyProgressResult CopyProgressRoutine(long TotalFileSize, long TotalBytesTransferred, long StreamSize, long StreamBytesTransferred, uint dwStreamNumber, CopyProgressCallbackReason dwCallbackReason,
                                                    IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);

    private enum CopyProgressResult : uint
    {
        PROGRESS_CONTINUE = 0,
        PROGRESS_CANCEL = 1,
        PROGRESS_STOP = 2,
        PROGRESS_QUIET = 3
    }

    private enum CopyProgressCallbackReason : uint
    {
        CALLBACK_CHUNK_FINISHED = 0x00000000,
        CALLBACK_STREAM_SWITCH = 0x00000001
    }

    [Flags]
    private enum CopyFileFlags : uint
    {
        COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
        COPY_FILE_NO_BUFFERING = 0x00001000,
        COPY_FILE_RESTARTABLE = 0x00000002,
        COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
        COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
    }

    #endregion

}

I client possono creare un oggetto della classe XCopy e chiamare il metodo copia / interrompi.

Se qualcuno si imbatte ancora in questo problema (10 anni dopo!) come ho fatto io, ho creato un wrapper attorno alle funzioni CopyFileEx e MoveFileWithProgress (come alcune delle risposte qui) con alcune funzionalità extra, utili (come asincrono, accesso controllo dei diritti, formattazione dei byte, copia delle directory ...)

Controlla qui - GitHub e Nuget

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top