I am using a bunch of synchron functions from my own "old" libary. These are used to backup files, compress them and upload them per example. For further use i would like to change these to async functions. Please forgive me my following long intro, but the problem needs a bit background..
i have found several information on how to convert this:
A great book : Concurrency in C# Cookbook from Stephen Cleary.
Here is example Pattern i am tryping to adept:
Also some posts here:
- C#: HttpClient, File upload progress when uploading multiple file as MultipartFormDataContent
System.Net.Http.HttpClient.PostAsync blocks and never returns
Main Points:
- Use Async /Await all the way done
- Don't wrap synchron Methods in asychron pattern with result or wait. Use await everywhere possible
- Wrap EAP Pattern Methods to Tasks
- Avoid Using Task.Run in Libaries
- Use ConfigureAwait(False) in Libaries
- Use Task.Run instead in your UI
- Use IProgress to post progress
My basic class looks like this:
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;
}
}
(For the progress i now use IProgress, so i removed the old lines within this class)
This class is used in most high level function of the library an should track all different actions, per example for Compress a Directory with 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);
...
...
}
As you can see i use the eventhandler to also send the object of my class, to be able to capture beside the progress also additional infos like action,status or status details. So now for my question:
For an async Task based approach this should be converted to a pattern like this:
public async Task<bool> CompressDirectoryTaskAsync(CompressItem actionItem,
IProgress<CompressItem> progress, CancellationToken cancellationToken)
This means i need to wrap the above function to this. The Eventhandler from SevenZipSharp uses EventArgs and is not descended from AsyncCompletedEventArgs. Is there a better approach?
UPDATE 2: I wrapped the Compressing Part into a Task, to be able to cancel it, if needed. The SevenZipCompressor doesn't support canceling. So normally i should avoid here within the libary a task.run, but don't know an alternative. I also did change to BeginCompressDirectoy, because it returns just after starting compressing instead of blocking the thread till done like CompressDirectory. So far the progress works, but canceling NOT. Just a little step to complete left ... hopefully you could help.
!! To test this function you only need to install the nuget package Squid-Box.SevenZipSharp !
So far i have tried to wrap the SevenZipCompressor like this:
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;
}
}