2015-01-11 5 views
1

Я делаю приложение, которое копирует удаленные сетевые файлы на локальный диск, и вся операция состоит из вызовов File.Copy(remotePath, localPath). Иногда операции копирования зависают или запускаются медленнее, но пока не выбрасывают никаких исключений, но я хочу остановить их, и нет способа проверить IsCancellationRequested из токена отмены, как и многие сообщения здесь рекомендуют для случаев, когда операции делятся , Что мне делать в этом случае для отмены задач?Отмена или отмена задач, которые состоят из одной нерушимой операции

ответ

1

Unfortuantly .NET не имеет управляемого способа отменить операцию копирования файлов, насколько мне известно, это может быть сделано только через собственные вызовы.

Нативный вызов, который вам нужно будет сделать, это FileCopyEx, он имеет параметр и функцию обратного вызова, которая позволяет отменить.

Вот как вы могли бы сделать это в .NET:

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
[return: MarshalAs(UnmanagedType.Bool)] 
private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, 
    CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref bool pbCancel, 
    CopyFileFlags dwCopyFlags); 

private const int CopyCanceledErrorCode = 1235; 

private static void Copy(string oldFile, string newFile, CancellationToken cancellationToken) 
{ 
    var cancel = false; 

    CopyProgressRoutine callback = 
     (size, transferred, streamSize, bytesTransferred, number, reason, file, destinationFile, data) => 
     { 
      if (cancellationToken.IsCancellationRequested) 
       return CopyProgressResult.PROGRESS_CANCEL; 
      else 
       return CopyProgressResult.PROGRESS_CONTINUE; 
     }; 

    if (!CopyFileEx(oldFile, newFile, callback, IntPtr.Zero, ref cancel, CopyFileFlags.COPY_FILE_RESTARTABLE)) 
    { 
     var error = Marshal.GetLastWin32Error(); 
     if (error == CopyCanceledErrorCode) 
      throw new OperationCanceledException(cancellationToken); 
       //Throws the more .NET friendly OperationCanceledException 
     throw new Win32Exception(error); 
    } 
    //This prevents the callback delegate from getting GC'ed before the native call is done with it. 
    GC.KeepAlive(callback); 
} 

Одно замечание, моя первая попытка это

private static void Copy(string oldFile, string newFile, CancellationToken cancellationToken) 
{ 
    bool cancel = false; 
    using (cancellationToken.Register(() => cancel = true)) 
    { 
     if (!CopyFileEx(oldFile, newFile, null, IntPtr.Zero, ref cancel, CopyFileFlags.COPY_FILE_RESTARTABLE)) 
     { 
      throw new Win32Exception(Marshal.GetLastWin32Error()); 
     } 
     GC.KeepAlive(cancel); 
    } 
} 

но это не вызывало копировать для отмены. Я не знаю, было ли это просто из-за того, что вы не можете изменить bool из делегата, или если это произошло потому, что CopyFileEx не проверяет как часто, чтобы увидеть, изменился ли bool. Использование метода обратного вызова для отмены копии намного надежнее.


APPENDEX:

Вот копия всех перечислений и делегатов, так что вам не нужно охотиться за ними на Pinvoke.net, как я сделал.

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 
} 

[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 //NT 6.0+ 
} 

delegate CopyProgressResult CopyProgressRoutine(
    long TotalFileSize, 
    long TotalBytesTransferred, 
    long StreamSize, 
    long StreamBytesTransferred, 
    uint dwStreamNumber, 
    CopyProgressCallbackReason dwCallbackReason, 
    IntPtr hSourceFile, 
    IntPtr hDestinationFile, 
    IntPtr lpData); 
1

Открыть оба файла FileStream и скопировать его в куски. Использовать, если вы можете использовать методы async.

Простая форма - считывать данные в буфер и записывать буфер . Расширенная версия должна считываться в одном буфере и одновременно записывать из второго буфера.

после или после каждого звонка на чтение/запись, операция отменяется.

Если есть проблема, u получит в какой-то момент timeoutexception в зависимости от свойства ReadTimeout WriteTimeout потока.

Размер буфера по умолчанию Windows составляет 4096 байт. Вот к методам выдвижения:

public static void WriteTo(this Stream source, Stream distiantion, int bufferSize = PNetIO.DefaultBufferSize, CancellationToken cancellationToken = default(CancellationToken)) 
    { 
     var buffer = new byte[bufferSize]; 
     int c; 
     while ((c = source.Read(buffer, 0, bufferSize)) > 0) 
     { 
      distiantion.Write(buffer, 0, c); 
      cancellationToken.ThrowIfCancellationRequested(); 
     } 
    } 

    public static async Task WriteToAsync(this Stream source, Stream distiantion, int bufferSize = PNetIO.DefaultBufferSize, CancellationToken cancellationToken = default(CancellationToken)) 
    { 
     if (!source.CanRead) 
      return; 

     var buffer = new Byte[bufferSize * 2]; 

     var c = await source.ReadAsync(buffer, bufferSize, bufferSize, cancellationToken).ConfigureAwait(false); 
     if (c <= 0) 
      return; 

     var w = distiantion.WriteAsync(buffer, bufferSize, c, cancellationToken); 
     while ((c = await source.ReadAsync(buffer, 0, bufferSize).ConfigureAwait(false)) > 0) 
     { 
      await w.ConfigureAwait(false); 
      w = distiantion.WriteAsync(buffer, 0, c, cancellationToken); 

      if ((c = await source.ReadAsync(buffer, bufferSize, bufferSize, cancellationToken).ConfigureAwait(false)) <= 0) 
       break; 

      await w.ConfigureAwait(false); 
      w = distiantion.WriteAsync(buffer, bufferSize, c, cancellationToken); 
     } 

     await w.ConfigureAwait(false); 
     await distiantion.FlushAsync(cancellationToken).ConfigureAwait(false); 
    } 

Вы можете использовать его как это:

var cancel = new CancellationTokenSource(); 

     using (var source = File.OpenRead("source")) 
     using (var dest = File.OpenWrite("dest")) 
      await source.WriteToAsync(dest, cancellationToken: cancel.Token); 
Смежные вопросы