Sunday, 9 October 2022

Parallel Programming 02

Previously we made a simple winRT App using the await/async keywords that updated progress bars and textboxs, let's use it again

create a simple event handler for the click event of the button.

async void Start_Button_Click(object sender, RoutedEventArgs e)
{
    //start the first long running task in parallel
    await LongRunningTask(Progress00_ProgresBar);

    //print something to the textbox
    TextBoxOne.Text = "First Progress Bar Complete";

    //show a message to the user
    await new MessageDialog("Good Times").ShowAsync();

    //start the second long running task in parallel
    await LongRunningTask(Progress01_ProgresBar);

    //print something to the second textbox
    TextBoxTwo.Text = "Second Progress Bar Complete";
}

and for our LongRunnting Task we simply made

public void LongRunningTask(ProgressBar pb)
{
    for (var i = 0; i <= 100; i+=20)
    {
        Task.Delay(1000).Wait();
        pb.Value = i;
    }
}

Now that worked great, that is unless you clicked the start button again before the whole process finished, kinda bunk what we need to do is be able to cancel our tasks if the start button is clicked. to do this we need to add a CancellationTokenSource, let's go ahead and create a property right on the page.

public sealed partial class MainPage : Page
{

    CancellationTokenSource cts { get; set; }
    //Our Code ...
}


with that done let's modify our start event handler

async void Start_Button_Click(object sender, RoutedEventArgs e)
{
    //Check if cancellation token source is instantiated
    if (this.cts != null) {
        //if it is, that means we're running and cancel
        cts.Cancel(true);
    }
    else
    {
        cts = new CancellationTokenSource();

        try
        {
            //start the first long running task in parallel
            await LongRunningTask(Progress00_ProgresBar, cts.Token);

            //print something to the textbox
            TextBoxOne.Text = "First Progress Bar Complete";

            //show a message to the user
            await new MessageDialog("Good Times").ShowAsync();

            //start the second long running task in parallel
            await LongRunningTask(Progress01_ProgresBar, cts.Token);

            //print something to the second textbox
            TextBoxTwo.Text = "Second Progress Bar Complete";
        }
        //catch cancellation exception from LongRunning function
        catch (OperationCanceledException ocex)
        {
            new MessageDialog("Operation Canceled, click start again").ShowAsync();
        }
        finally
        {
            //canceled or finished set the cancellation token source to null
            this.cts = null;
        }
    }
}

Next we modify our long running function to accept the cancellation token and use it to terminate the task if it's signaled to cancel.

async Task LongRunningTask(ProgressBar pb, CancellationToken token)
{
    for (var i = 0; i <= 100; i += 20)
    {
        await Task.Delay(1000);

        //if cancel signal sent, break the loop
        if (token.IsCancellationRequested)
            break;
        await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => pb.Value = i);
    }
    //throw cancelation exception
    token.ThrowIfCancellationRequested();

}

we pass in our token and then if we signal it, our long running task will stop and throw an cancellation request winch will halt our start event handler.