Конвертировать CopyFileEx в шаблон задачи - PullRequest
3 голосов
/ 29 марта 2012

Читал об асинхронности и задачах и пытался преобразовать метод CopyFileEx через PInvoke в шаблон Task с прогрессом.У меня проблемы с прогрессом.

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 Ответ

1 голос
/ 06 апреля 2012

Я думаю, что самое простое решение - переместить обратный вызов CopyProgressHandler в класс пользовательских аргументов. В этом случае вы можете использовать ucarg.CopyProgressHandler в качестве вашего CopyProgressRoutine и вызывать методы для ссылки IProgress, которую вы сохранили в своем классе пользовательских аргументов. Возможно, вы также можете переместить флаг m_bCancel в этот класс.

При таком подходе вы избегаете сортировки ваших данных.

...