如何用任务包装 EAP 模式方法和 IProgress
Howto wrap EAP Pattern Method and IProgress with Task
我正在使用我自己的 "old" 库中的一堆同步函数。这些用于备份文件,压缩它们并根据示例上传它们。为了进一步使用,我想将它们更改为异步函数。请原谅我接下来的冗长介绍,但问题需要一些背景知识..
我找到了一些关于如何转换它的信息:
一本好书:Stephen Cleary 的 C# 并发指南。
这是我正在尝试掌握的示例模式:
这里还有一些 post:
要点:
- 完全使用 Async /Await
- 不要将同步方法包装在带有结果或等待的异步模式中。尽可能使用 await
- 将 EAP 模式方法包装到任务中
- 避免在库中使用 Task.Run
- 在库中使用 ConfigureAwait(False)
- 在 UI
中使用 Task.Run
- 使用 IProgress post 进步
我的基本 class 是这样的:
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,所以我删除了这个 class 中的旧行)
这个 class 用于库的大多数高级功能,应该跟踪所有不同的操作,例如使用 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);
...
...
}
如您所见,我使用事件处理程序还发送了我的 class 的对象,以便能够在进度之外捕获其他信息,例如操作、状态或状态详细信息。
那么现在我的问题是:
对于基于异步任务的方法,应将其转换为如下模式:
public async Task<bool> CompressDirectoryTaskAsync(CompressItem actionItem,
IProgress<CompressItem> progress, CancellationToken cancellationToken)
这意味着我需要将上述函数包装到此。
SevenZipSharp 的 Eventhandler 使用 EventArgs,而不是 AsyncCompletedEventArgs 的后代。
有更好的方法吗?
更新 2:
我将压缩部分包装到一个任务中,以便在需要时能够取消它。
SevenZipCompressor 不支持取消。所以通常我应该避免在库中使用 a task.run,但不知道其他选择。
我也确实更改为 BeginCompressDirectoy,因为它 returns 只是在开始压缩之后而不是像 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;
}
}
我最终使用了这个 post 中的以下解决方案:
A reusable pattern to convert event into task。
这些功能现在可以等待,但不能取消..到目前为止找不到实现此目的的方法
我在 CompressDirectoryTaskAsync 中像这样使用 TaskExt Class:
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);
...
...
}
我正在使用我自己的 "old" 库中的一堆同步函数。这些用于备份文件,压缩它们并根据示例上传它们。为了进一步使用,我想将它们更改为异步函数。请原谅我接下来的冗长介绍,但问题需要一些背景知识..
我找到了一些关于如何转换它的信息:
一本好书:Stephen Cleary 的 C# 并发指南。
这是我正在尝试掌握的示例模式:
这里还有一些 post:
要点:
- 完全使用 Async /Await
- 不要将同步方法包装在带有结果或等待的异步模式中。尽可能使用 await
- 将 EAP 模式方法包装到任务中
- 避免在库中使用 Task.Run
- 在库中使用 ConfigureAwait(False)
- 在 UI 中使用 Task.Run
- 使用 IProgress post 进步
我的基本 class 是这样的:
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,所以我删除了这个 class 中的旧行)
这个 class 用于库的大多数高级功能,应该跟踪所有不同的操作,例如使用 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);
...
...
}
如您所见,我使用事件处理程序还发送了我的 class 的对象,以便能够在进度之外捕获其他信息,例如操作、状态或状态详细信息。 那么现在我的问题是:
对于基于异步任务的方法,应将其转换为如下模式:
public async Task<bool> CompressDirectoryTaskAsync(CompressItem actionItem,
IProgress<CompressItem> progress, CancellationToken cancellationToken)
这意味着我需要将上述函数包装到此。 SevenZipSharp 的 Eventhandler 使用 EventArgs,而不是 AsyncCompletedEventArgs 的后代。 有更好的方法吗?
更新 2: 我将压缩部分包装到一个任务中,以便在需要时能够取消它。 SevenZipCompressor 不支持取消。所以通常我应该避免在库中使用 a task.run,但不知道其他选择。 我也确实更改为 BeginCompressDirectoy,因为它 returns 只是在开始压缩之后而不是像 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;
}
}
我最终使用了这个 post 中的以下解决方案: A reusable pattern to convert event into task。 这些功能现在可以等待,但不能取消..到目前为止找不到实现此目的的方法
我在 CompressDirectoryTaskAsync 中像这样使用 TaskExt Class:
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);
...
...
}