2

I've got a file organizer application I'm working on. The files are regularly 500MB to 2GB. Everything works fine, but it is extremely annoying that the application "Stops Responding." What I'd like to do is a byte by byte or meg by meg copy with some Application.DoEvents() in there after each read/write action. Something along these lines, I don't know what the actual classes to use would be so I'm just going to make some stuff up :)

private void CopyFile(string inFilename, string outFilename)
{
    FileReader inReader(inFilename);
    FileWriter outWriter(outFilename, FileMode.OpenOrCreate);

    byte theByte;
    while (theByte = inReader.ReadByte())
    {
        outWriter.WriteByte(theByte, WriteMode.Append);
        UpdateProgressBar();
        Application.DoEvents();
    }

    inReader.CloseFile();
    outWriter.CloseFile();
}

I know this seems like it should be a simple thing to do but I for the life of me can't seem to find any kind of example for doing this without using direct API calls or whatever. I've got to be missing something here so if anyone could get me on the right track here I'd really appreciate it.

Thanks in advance!

Aaron
  • 802
  • 7
  • 17
  • I guess what I really want out of this is to be able to see the progress as the file is copied. I could put in threads but then I still wouldn't have any kind of feedback onscreen in real time with the copy. Perhaps I wasn't clear on that point. Thanks for the quick response! – Aaron May 10 '09 at 01:25
  • How about having the copy operation in a separate thread, then having the main application poll the file sizes of the files being worked with and use that to update the progress. Thoughts? – Aaron May 10 '09 at 01:28
  • I think that a worker thread can update the progress bar, if it does it carefully (using `BeginInvoke`). – ChrisW May 10 '09 at 02:01

8 Answers8

6

You should use a BackgroundWorker on your form to do the copying. It will allow the file copies to be done on a separate thread and let your UI be responsive. There is some added complexity, but the BackgroundWorker takes care of a lot of the plumbing for you. But, there are plenty of examples of what you want to do.

JP Alioto
  • 44,864
  • 6
  • 88
  • 112
3

You need to use a BackgroundWorkerThread to accomplish this. Here's a very good example of how to do that: Copy File Using Background Worker Threads

Jose Basilio
  • 50,714
  • 13
  • 121
  • 117
3

I'd want to use the CopyFileEx function. If an analog to that function doesn't exist in the managed framework library, then Google for how to use it anyway: maybe an article like http://www.thetechscene.com/2008/09/copyfileex-with-progress-callback-in-c-using-pinvoke/

My reason for wanting to use CopyFileEx is that I assume it's implemented in the O/S kernel, with the data being copy from one file to another within the file system driver, without using user memory (let alone managed memory).

ChrisW
  • 54,973
  • 13
  • 116
  • 224
1

Threading.ThreadPool.QueueUserWorkitem should get you well on your way.

Daniel
  • 10,864
  • 22
  • 84
  • 115
1

One approach is to perform the copy operation in a separate thread. Your main application will continue to run normally while the thread performs the work of copying the file. You'll of course want to add communication between the thread and the main application so you can update your progress bar or similar feedback mechanism.

If you'd rather not deal with multiple threads, another approach is to create a class that contains state variables for the copy operation and a member function that's called periodically from your main application in order to copy a certain number of bytes each time it's called.

Adrian Lopez
  • 1,695
  • 1
  • 16
  • 22
1

You have two problems here. The first is that the GUI thread is not responsive while copying large files. You should use a background thread to solve that, as others have suggested.

The other problem is that your current file copying routine does not support a progress callback function. The accepted answer to the question below has the information you need to write your own solution:

Can I show file copy progress using FileInfo.CopyTo() in .NET?

EDIT: I just found this wrapper class for CopyFileEx. I tested it and it works great!

using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;

namespace FileCopyTest {
    public sealed class FileRoutines {
        public static void CopyFile(FileInfo source, FileInfo destination) {
            CopyFile(source, destination, CopyFileOptions.None);
        }

        public static void CopyFile(FileInfo source, FileInfo destination,
            CopyFileOptions options) {
            CopyFile(source, destination, options, null);
        }

        public static void CopyFile(FileInfo source, FileInfo destination,
            CopyFileOptions options, CopyFileCallback callback) {
            CopyFile(source, destination, options, callback, null);
        }

        public static void CopyFile(FileInfo source, FileInfo destination,
            CopyFileOptions options, CopyFileCallback callback, object state) {
            if (source == null) throw new ArgumentNullException("source");
            if (destination == null)
                throw new ArgumentNullException("destination");
            if ((options & ~CopyFileOptions.All) != 0)
                throw new ArgumentOutOfRangeException("options");

            new FileIOPermission(
                FileIOPermissionAccess.Read, source.FullName).Demand();
            new FileIOPermission(
                FileIOPermissionAccess.Write, destination.FullName).Demand();

            CopyProgressRoutine cpr = callback == null ?
                null : new CopyProgressRoutine(new CopyProgressData(
                    source, destination, callback, state).CallbackHandler);

            bool cancel = false;
            if (!CopyFileEx(source.FullName, destination.FullName, cpr,
                IntPtr.Zero, ref cancel, (int)options)) {
                throw new IOException(new Win32Exception().Message);
            }
        }

        private class CopyProgressData {
            private FileInfo _source = null;
            private FileInfo _destination = null;
            private CopyFileCallback _callback = null;
            private object _state = null;

            public CopyProgressData(FileInfo source, FileInfo destination,
                CopyFileCallback callback, object state) {
                _source = source;
                _destination = destination;
                _callback = callback;
                _state = state;
            }

            public int CallbackHandler(
                long totalFileSize, long totalBytesTransferred,
                long streamSize, long streamBytesTransferred,
                int streamNumber, int callbackReason,
                IntPtr sourceFile, IntPtr destinationFile, IntPtr data) {
                return (int)_callback(_source, _destination, _state,
                    totalFileSize, totalBytesTransferred);
            }
        }

        private delegate int CopyProgressRoutine(
            long totalFileSize, long TotalBytesTransferred, long streamSize,
            long streamBytesTransferred, int streamNumber, int callbackReason,
            IntPtr sourceFile, IntPtr destinationFile, IntPtr data);

        [SuppressUnmanagedCodeSecurity]
        [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool CopyFileEx(
            string lpExistingFileName, string lpNewFileName,
            CopyProgressRoutine lpProgressRoutine,
            IntPtr lpData, ref bool pbCancel, int dwCopyFlags);
    }

    public delegate CopyFileCallbackAction CopyFileCallback(
        FileInfo source, FileInfo destination, object state,
        long totalFileSize, long totalBytesTransferred);

    public enum CopyFileCallbackAction {
        Continue = 0,
        Cancel = 1,
        Stop = 2,
        Quiet = 3
    }

    [Flags]
    public enum CopyFileOptions {
        None = 0x0,
        FailIfDestinationExists = 0x1,
        Restartable = 0x2,
        AllowDecryptedDestination = 0x8,
        All = FailIfDestinationExists | Restartable | AllowDecryptedDestination
    }
}
Community
  • 1
  • 1
Kyle Gagnet
  • 2,294
  • 2
  • 20
  • 27
1

In addition to running a background thread, you should note that you're copying 512M-2G of data one byte at a time. This will translate into up to 2 BILLION calls to ReadByte and WriteByte. Hopefully those calls buffer somewhere so you don't make 2 BILLION managed to unmanaged transitions, but even so that will surely add up.

Memory isn't free, but it's sure as heck is cheap. Allocate a buffer (maybe 16K-64K) and copy in chunks. No, the code isn't as simple as you will need to handle one case of not reading the entire block, but I'd rather to 2G/64K method invocations over 2G.

plinth
  • 48,267
  • 11
  • 78
  • 120
0

What you really want to do is have a multi-threaded application and do the file copy in a background thread, that way your main thread will not be tied up.

JonnyBoats
  • 5,177
  • 1
  • 36
  • 60