As an amateur coder, I find the world of Async/await and Tasks a little baffling. Most examples I found used pre-existing Async methods such as HttpClient.ReadAsStringAsync
However, if you tried to implement them with your own long running, synchronous methods, particularly ones which are required not to block the main thread and also to provide update of progress, things seem to get tricky fast. This all came to a head when I was trying to write a helper class around one of DotNetZip’s methods which unfortunately aren’t asynchronous. The code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
using Ionic.Zip; using Stigzler.Utility.NetUpdate.EventArgs; using System; using System.IO; using System.Threading.Tasks; namespace Stigzler.Utility.NetUpdate.Helpers { public delegate void NewEntryExtractionStartedHandler(NewEntryExtractionStartedEventArgs e); public delegate void EntryExtractionProgressedHandler(EntryExtractionProgressedEventArgs e); internal class Unzipper { public event NewEntryExtractionStartedHandler NewEntryExtractionStarted; public event EntryExtractionProgressedHandler EntryExtractionProgressed; private FileInfo archiveFile; private DirectoryInfo targetDirectory; private Progress<ExtractProgressEventArgs> unzipProgress = new Progress<ExtractProgressEventArgs>(); public Unzipper(FileInfo archiveFile, DirectoryInfo targetDirectory) { this.archiveFile = archiveFile; this.targetDirectory = targetDirectory; } public async Task Unzip() { unzipProgress.ProgressChanged += Progress_ProgressChanged; await PerformUnzipAsync(); } async Task PerformUnzipAsync() { await Task.Run(() => PerformUnzip(archiveFile.FullName, targetDirectory.FullName, unzipProgress)); } private Task PerformUnzip(string payloadFile, string targetDir, IProgress<ExtractProgressEventArgs> progress) { ZipFile zFile = new ZipFile(payloadFile); zFile.ExtractProgress += (s, e) => { progress.Report(e); }; zFile.ExtractAll(targetDir, ExtractExistingFileAction.OverwriteSilently); return Task.CompletedTask; } private void Progress_ProgressChanged(object sender, ExtractProgressEventArgs e) { if (e.EventType == ZipProgressEventType.Extracting_BeforeExtractEntry) { NewEntryExtractionStarted.Invoke(new NewEntryExtractionStartedEventArgs { CurrentEntry = e.CurrentEntry, EntriesExtracted = e.EntriesExtracted, TotalEntries = e.EntriesTotal }); } else if (e.EventType == ZipProgressEventType.Extracting_EntryBytesWritten) { EntryExtractionProgressed.Invoke(new EntryExtractionProgressedEventArgs { CurrentEntry = e.CurrentEntry, BytesTransferred = e.BytesTransferred, TotalBytesTransferred = e.BytesTransferred }); } else if (e.EventType == ZipProgressEventType.Extracting_AfterExtractAll) { unzipProgress.ProgressChanged -= Progress_ProgressChanged; } } } } |
The key mechanics are the Task.Run
and the various async/await
components. Firstly, the Task.Run
runs the PerformUnzip
Method on a separate thread. However, this on its own would mean the methods would run asynchronously and the code would just continue on the main thread. Initially, I just placed:
Task.Run(() => PerformUnzip(archiveFile.FullName, targetDirectory.FullName, unzipProgress))
in the Constructor. You couldn’t just put an await in front of it in the constructor. The trick was to put an intermediary method ‘between’ Unzip
and PerformUnzip
and have this perform the call there instead:
await Task.Run(() => PerformUnzip(archiveFile.FullName, targetDirectory.FullName, unzipProgress));
Another thing of interest to Task newbies like me is the Progress
Type – this is used much like the old ReportProgress
of the old BackGround worker days. Hopefully it’ll be clear how it’s used here (although the implementations a little complex due to the ExtractProgressEventArgs
class being a little convoluted).
In your main routine, you just call it thus:
await unzipper.Unzip();
(ahhhh.. satisfying!) 😏
Leave a Reply