2012-03-29 3 views
3

비동기 및 작업에 대해 읽고 PInvoke를 통해 CopyFileEx 메서드를 진행중인 작업 패턴으로 변환하려고 시도했습니다. 진행 부분에 문제가 있습니다.CopyFileEx를 작업 패턴으로 변환

CopyFileEx에는 포인터를 취하는 lpData라는 매개 변수가있는 CopyProgressRoutine이라는 콜백이 있습니다. 진행 상황을보고 할 수 있도록 내 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); 
       } 
      } 
     }); 
    } 
} 
+0

DoWorkAsync를 동기로 설정해야합니다. 동기 동작을 중심으로 스레드를 래핑하는 것은 안티 패턴입니다. 발신자가 직접 할 수 있습니다. – usr

답변

1

가장 쉬운 해결책은 CopyProgressHandler 콜백을 사용자 인수 클래스로 이동하는 것입니다. 이 경우 ucarg.CopyProgressHandler를 CopyProgressRoutine으로 사용하고 사용자 인수 클래스에 저장 한 IProgress 참조에서 메소드를 호출 할 수 있습니다. 아마 m_bCancel 플래그를 해당 클래스로 옮길 수 있습니다.

이 방법을 사용하면 데이터 마샬링을 피할 수 있습니다.