首页 > 解决方案 > 如何用任务包装 EAP 模式方法和 IProgress

问题描述

我正在使用我自己的“旧”库中的一堆同步函数。这些用于备份文件,压缩它们并按示例上传它们。为了进一步使用,我想将这些更改为异步函数。请原谅我下面的长篇介绍,但这个问题需要一些背景知识..

我找到了一些关于如何转换它的信息:

一本好书:Stephen Cleary 的 C# Cookbook 中的并发。

这是我尝试熟练使用的示例模式:

这里还有一些帖子:

要点:

我的基本课程如下所示:

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 那样阻塞线程直到完成。到目前为止,进展有效,但取消 NOT。只需一小步即可完成左侧...希望您能提供帮助。

!!要测试此功能,您只需要安装 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;
            }
        }

标签: c#asynchronousasync-awaittaskiprogress

解决方案


我最终使用了这篇文章中的以下解决方案: A reusable pattern to convert event into task。这些功能现在是 awaitbale,但不是 cancelbale..到目前为止还没有找到实现这一目标的方法

我在 CompressDirectoryTaskAsync 中使用这样的 TaskExt 类:

   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); 
       ...
       ...
    }

推荐阅读