Как обернуть метод шаблона EAP и IProgress с помощью задачи

Я использую кучу синхронных функций из моей "старой" библиотеки. Они используются для резервного копирования файлов, их сжатия и загрузки, например. Для дальнейшего использования я хотел бы изменить их на асинхронные функции. Пожалуйста, простите меня за мое следующее длинное вступление, но проблема требует некоторой предыстории..

Я нашел несколько информации о том, как это преобразовать:

Замечательная книга: "Поваренная книга по параллелизму в C#" от Стивена Клири.

Вот пример шаблона, который я пытаюсь освоить:

Также несколько сообщений здесь:

Основные моменты:

  • Полностью используйте Async /Await
  • Не переносите методы синхронизации в асинхронный шаблон с результатом или ожиданием. Используйте ожидание везде, где это возможно
  • Сворачивание методов шаблона EAP в задачи
  • Избегайте использования Task.Run в библиотеках
  • Используйте ConfigureAwait(False) в библиотеках
  • Вместо этого используйте Task.Run в пользовательском интерфейсе
  • Используйте IProgress для публикации прогресса

Мой базовый класс выглядит так:

public class CompressItem
{
    public string ArchiveName { get; set; }
    public string Status { get; set; }
    public string StatusDetails { get; set; }
    public string SourcePath{ get; set; }
    public string ErrorText { get; set; }
    public int Percent { get; set; }
    public bool IsFinished { get; set; }
    public bool IsCancelling { get; set; }
    public MyClass()
    {
      FileName = Status = SourcePath = StatusDetails = ErrorText = "";
      Precent = 0;
      IsFinished = false;
      IsCancelling = false;
    }
}

(Для прогресса я теперь использую IProgress, поэтому я удалил старые строки в этом классе)

Этот класс используется в самой высокоуровневой функции библиотеки и должен отслеживать все различные действия, например, для сжатия каталога с помощью SevenZipSharp:

public bool CompressDirectory(CompressItem actionItem) 
{
    // Do some stuff with MyClass to get sourcePath and archiveFileName
    //
    ...
    SevenZipCompressor compressor = new SevenZipCompressor();

    // Add Event Handler
    compressor.Compressing += new EventHandler<ProgressEventArgs>((sender, args) =>
                    { CompressItem_ProgressChanged(sender, args, actionItem); });
    compressor.CompressionFinished += new EventHandler<EventArgs>((sender, args) =>
                    { CompressItem_FileCompleted(sender, args, actionItem); });
    compressor.FileCompressionStarted += new EventHandler<FileNameEventArgs>((sender, args) =>
                    { CompressItem_FileCompressionStarted(sender, args, actionItem); });
    // Start Compression
    compressor.CompressDirectory(sourcePath, archiveFileName);
   ...
   ...
}

Как вы можете видеть, я использую обработчик событий, чтобы также отправлять объект моего класса, чтобы иметь возможность фиксировать помимо прогресса также дополнительную информацию, такую ​​как действие, статус или детали статуса. Итак, теперь мой вопрос:

Для подхода, основанного на асинхронных задачах, это должно быть преобразовано в такой шаблон:

    public async Task<bool> CompressDirectoryTaskAsync(CompressItem actionItem,
       IProgress<CompressItem> progress, CancellationToken cancellationToken)

Это означает, что мне нужно обернуть указанную выше функцию этим. Обработчик событий из SevenZipSharp использует EventArgs и не является потомком AsyncCompletedEventArgs. Есть ли лучший подход?

ОБНОВЛЕНИЕ 2: я завернул часть сжатия в задачу, чтобы иметь возможность отменить ее, если это необходимо. SevenZipCompressor не поддерживает отмену. Поэтому обычно я должен избегать здесь, в библиотеке, task.run, но не знаю альтернативы. Я также перешел на BeginCompressDirectoy, потому что он возвращается сразу после начала сжатия, а не блокирует поток до тех пор, пока он не будет выполнен, как CompressDirectory. Пока прогресс работает, но отмены НЕ. Остался всего один шаг... надеюсь, ты сможешь помочь.

!! Чтобы протестировать эту функцию, вам нужно только установить пакет nuget Squid-Box.SevenZipSharp!

До сих пор я пытался обернуть SevenZipCompressor следующим образом:

public static Task TestCompressDirectoryTaskAsync(SevenZipCompressor compressor, 
    CompressItem actionItem, IProgress<CompressItem> progress, 
    CancellationToken cancellationToken)
     {  
          // little setup:
          // set 7z.dll path x64/x86
           string path = Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory), Environment.Is64BitProcess ? "x64" : "x86", "7z.dll");
           SevenZipBase.SetLibraryPath(path);
           // for testing use this
           //SevenZipCompressor compressor = new SevenZipCompressor();
           // specifiy 7z format
           compressor.ArchiveFormat = OutArchiveFormat.SevenZip;
           // use lzma2
           compressor.CompressionMethod = CompressionMethod.Lzma2;
           compressor.CompressionMode = CompressionMode.Create;
           compressor.TempFolderPath = System.IO.Path.GetTempPath();                         
           var tcs = new TaskCompletionSource<EventArgs>();
            // Registering a lambda into the cancellationToken
            cancellationToken.Register(() =>
            {
                // We received a cancellation message, cancel the TaskCompletionSource.Task
                tcs.TrySetCanceled();
            });
            EventHandler<EventArgs> handler = null;               
            try
            { 
                var task = Task.Run(() =>
                {
                    compressor.CompressionFinished += handler = (sender, args) => { tcs.TrySetResult(args); };
                    compressor.Compressing += (sender, args) =>
                    {
                        try
                        {
                            //Check if cancellation has been requested
                            if (cancellationToken != null)
                            {
                                if (cancellationToken.IsCancellationRequested)
                                {
                                    tcs.TrySetCanceled();
                                    //throw new Exception("Cancel Requested");
                                    cancellationToken.ThrowIfCancellationRequested();
                                    //tcs.TrySetException(new Exception("Cancel Requested"));
                                }
                            }

                            //Report progress
                            if (progress != null)
                            {
                                actionItem.IsFinished = false;
                                actionItem.Status = "Compressing in Progess .."
                                actionItem.Percent = args.PercentDone;
                                progress.Report(actionItem);
                            }
                        }
                        catch (Exception e)
                        {
                            tcs.TrySetException(e);
                        }
                    };
                    compressor.BeginCompressDirectory(actionItem.SourcePath, actionItem.ArchiveName);
                    return tcs.Task;
                },cancellationToken);

                return task;
            }
                catch (Exception e)
            {
                compressor.CompressionFinished -= handler;
                tcs.TrySetException(e);
                tcs.TrySetCanceled();
                throw;
            }
        }

1 ответ

Решение

В итоге я использовал следующее решение из этого поста: многоразовый шаблон для преобразования события в задачу. Функции теперь awaitbale, но не cancelbale.. пока не нашел способа добиться этого

Я использую класс TaskExt в моем CompressDirectoryTaskAsync следующим образом:

   public static Task CompressDirectoryTaskAsync(SevenZipCompressor compressor, 
    CompressItem actionItem, IProgress<CompressItem> progress, 
    CancellationToken cancellationToken)
    {
        // Do some stuff with MyClass to get sourcePath and archiveFileName
        //
        ...

       // Add Event Handler and Progress
       compressor.Compressing += new EventHandler<ProgressEventArgs>((sender, args) =>
       { CompressItem_ProgressChanged(sender, args, actionItem, progress); });

       compressor.CompressionFinished += new EventHandler<EventArgs>((sender, args) =>
       { CompressItem_FileCompleted(sender, args, actionItem, progress); });

       compressor.FileCompressionStarted += new EventHandler<FileNameEventArgs>((sender, args) =>
       { CompressItem_FileCompressionStarted(sender, args, actionItem, progress); });


       // Start Compression
       await TaskExt
        .FromEvent<EventArgs>()
        .WithHandlerConversion(handler => new EventHandler<EventArgs>(handler))
        .Start(
         handler => compressor.CompressionFinished += handler,
         () => compressor.BeginCompressDirectory(actionItem.SourcePath, archiveFileName),
         handler => compressor.CompressionFinished -= handler,
         cancellationToken).ConfigureAwait(false); 
       ...
       ...
    }
Другие вопросы по тегам