将CopyFileEx转换为任务模式

本文关键字:任务 模式 转换 CopyFileEx | 更新日期: 2023-09-27 18:29:31

一直在阅读有关异步和任务的内容,并试图通过PInvoke将CopyFileEx方法转换为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);
                }
            }
        });
    }
}

将CopyFileEx转换为任务模式

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

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