The other day Stephen Toub of MSFT published a blog on cooperative pausing async methods. It’s an awesome post, and a much better way of pausing in async methods than what I was doing (which was a loop where I checked a flag and called Task.Delay). He created a type called a PauseToken, which works much the same way as a CancellationToken. I have thus updated all the code that supports pausing async methods in my application to use this.
I now present some code that uses this, as well as running an async task on all the files in the directory. There are several places in the application that do this, so I’ve just randomly picked one of them.
Again, the source, for this and a whole bunch of other stuff, is here: RomyView.zip
I have a custom SaveImageDialog, that allows image conversion as follows: (This is not a real save dialog, it’s my own creation; it’s not production code, and not really important to the main topic of this post. I just want to illustrate how to get there in the application.)
Normally, the dialog, which I got to by clicking the Save As button on my image viewer, looks like this:
Clicking the file type dropdown changes what it will do as follows:
That is, it filtered the thumbnail view by PNG files. (There aren’t any in the directory.) It then hid the controls on the dialog that are specific to JPG format, and made the button that will convert all images in the directory visible, as well as the checkbox to enable deleting the source image(s) after conversion.
The code that runs if you convert all the files in the directory is this:
As usual, it calls several extension methods defined elsewhere in the solution, as well as common methods that I call frequently in Romy.Core, which is a class library of my commonly used classes and extension methods. My progress dialog exposes the PauseTokenSource type of the blog post mentioned at the start of this post, to enable waiting asynchronously on it, and the Pause button simply sets the token to paused or unpaused. It also uses a standard CancellationToken for cancellation.
ForeachAsync is another extension method from an older blog post by Stephen Toub (I use a lot of his sample code). The code to support saving to icon format is something I forgot to remove; it’s not meant to be there. ImageManager is is my own bastard creation, to enable managing GDI+ references, such that they can be passed around to async Tasks without worrying about the references going out of scope, leading to the fabulous “An unexpected error has occurred in GDI+”, or the even more useful “Object is in use elsewhere”, and the various methods to save images asynchronously are extension methods defined in Romy.Core.
The progress dialog’s design is very simple, so I won’t paste any of its code here. It has a Count property, initialized upfront, and each time its public UpdateProgress method is called with a file name, it displays the file name and updates its progress, which will be 100% when it reaches Count.
It also perhaps worth noting that the code called by the dialog’s Cancel button first unpauses the dialog if it is paused, so that it is possible to cancel while paused. I found this was an oversight when I first wrote code like this. It may seem obvious that this is required now, but when writing the actual code that supports both cancellation as well as pause, it is very easy to miss that it needs to support immediate cancellation from the paused state.
All of this happens in a ThreadPool thread, which of course creates a slight problem: You can’t update the user interface from another thread. Hence all of my forms inherit the InvokeOnUiThread method, which simply does the call using a captured UI-thread-synchronized synchronization context. Thus the code to update the progress dialog calls this method, which effectively switches to the UI-thread.