将CopyFileEx转换为任务模式

时间:2012-03-29 19:22:57

标签: c# asynchronous task file-copying

一直在阅读有关异步和任务的内容,并尝试通过PInvoke将CopyFileEx方法转换为具有进度的任务模式。我对进度部分有疑问。

CopyFileEx有一个名为CopyProgressRoutine的回调,它有一个名为lpData的参数,它带有一个指针。我以为我可以使用它传递我的IProgress界面,以便我可以报告进度。然而,结果我必须使用结构,而不是类。任何想法我怎么能让这个工作,或者我是否朝着完全错误的方向前进?

public class ProgressReportAsync
{
    public int PercentDone { get; set; }
    public string InfoText { get; set; }

    public void setProgress(long _progress, long _total)
    {
        PercentDone = Convert.ToInt32((_progress * 100) / _total);
        InfoText = PercentDone + "% complete."; ;
    }
}

class FileCopyAsync
{
    class UserCallbackArg
    {
        public CancellationToken ct;
        public IProgress<ProgressReportAsync> prg;

        public UserCallbackArg(CancellationToken _ct, IProgress<ProgressReportAsync> _prg)
        {
            ct = _ct;
            prg = _prg;
        }

        public UserCallbackArg() { }
    }

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, CopyProgressRoutine lpProgressRoutine, Object lpData, ref bool 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);

    [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,
        COPY_FILE_COPY_SYMLINK = 0x00000800,
        COPY_FILE_NO_BUFFERING = 0x00001000
    }

    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
    }

    private static bool m_bCancel;

    private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long StreamByteTrans, uint dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
    {
        switch (reason)
        {
            case CopyProgressCallbackReason.CALLBACK_CHUNK_FINISHED:

                UserCallbackArg ucarg = (UserCallbackArg)Marshal.PtrToStructure(lpData, typeof(UserCallbackArg));

                IProgress<ProgressReportAsync> prg = ucarg.prg;
                ProgressReportAsync prgReport = new ProgressReportAsync();

                prgReport.setProgress(transferred, total);
                prg.Report(prgReport);

                if (ucarg.ct.IsCancellationRequested)
                {
                    m_bCancel = true;
                }

                return m_bCancel ? CopyProgressResult.PROGRESS_CANCEL : CopyProgressResult.PROGRESS_CONTINUE;

            default:
                return CopyProgressResult.PROGRESS_CONTINUE;
        }
    }

    public FileCopyAsync() { }

    public Task DoWorkAsync(string _from, string _to, CancellationToken ct, IProgress<ProgressReportAsync> prg)
    {
        return TaskEx.Run(() =>
        {
            bool copyResult;

            if (File.Exists(_to))
            {
                //throw new Exception("File already exists: " + _to);
            }

            if (!File.Exists(_from))
            {
                throw new FileNotFoundException(_from);
            }

            FileInfo fi = new FileInfo(_from);

            m_bCancel = false;

            UserCallbackArg ucarg = new UserCallbackArg(ct, prg);
            GCHandle handle = GCHandle.Alloc(ucarg, GCHandleType.Pinned);
            IntPtr ptr = handle.AddrOfPinnedObject();


            if (fi.Length > (1024 * 1024 * 100))
            {
                //Greater than 100mb then no buffer flag added
                copyResult = CopyFileEx(_from, _to, new CopyProgressRoutine(CopyProgressHandler), ptr, ref m_bCancel, (CopyFileFlags.COPY_FILE_RESTARTABLE & CopyFileFlags.COPY_FILE_FAIL_IF_EXISTS & CopyFileFlags.COPY_FILE_NO_BUFFERING));
            }
            else
            {
                copyResult = CopyFileEx(_from, _to, new CopyProgressRoutine(CopyProgressHandler), ptr, ref m_bCancel, (CopyFileFlags.COPY_FILE_RESTARTABLE & CopyFileFlags.COPY_FILE_FAIL_IF_EXISTS));
            }
            if (!copyResult)
            {
                int error = Marshal.GetLastWin32Error();

                if (m_bCancel && (error == 1235))
                {
                    return;
                }
                else
                {
                    Win32Exception ex = new Win32Exception(error);
                    throw new Win32Exception(error);
                }
            }
        });
    }
}

1 个答案:

答案 0 :(得分:1)

我认为最简单的解决方案是将CopyProgressHandler回调移动到您的用户参数类。在这种情况下,您可以使用ucarg.CopyProgressHandler作为CopyProgressRoutine,并在存储在用户参数类中的IProgress引用上调用方法。您也可以将m_bCancel标志移动到该类。

使用这种方法可以避免数据的编组。