The Task Parallel Library Sampler – Part 11: Cancelling Threads with the CancellationTokenSource – The MVVM

Previous Post in this series:
Part 10: Loop control of the Parallel.For and .ForEach

Next up we’ll work on cancelling threads with the CancellationTokenSource. This is really a two parter where the first part will deal with the changes I had to make to integrate a new view and the second part covering the sample model. There is an update set of code available.

Since we’re going to want to cancel a thread once we’ve started it we’ll need a mechanism for the user to do so. Working from the model to the view model to the view we’ll see how I do this.

CancellationSample

public class CancellationSample : Sample
{
	public override string SampleName
	{
		get { return "Cancellation Sample"; }
	}

	public override bool ImageRequired
	{
		get { return false; }
	}

	bool isRunning = false;
	public bool IsRunning
	{
		get { return this.isRunning; }
		set
		{
			if (this.isRunning != value)
			{
				this.isRunning = value;
				this.RaisePropertyChanged("IsRunning");
			}
		}
	}
			
	public CancellationTokenSource CancellationTokenSource { get; set; }

	private static Random ran = new Random();

	public override void Run(System.Drawing.Bitmap bmp = null, Action<string> UpdateLog = null)
	{
		Stopwatch s = new Stopwatch();
		s.Start();

		IsRunning = true;

		CancellationTokenSource = new CancellationTokenSource();
		CancellationTokenSource.Token.Register(() => { IsRunning = false; });

		var options = new ParallelOptions { CancellationToken = CancellationTokenSource.Token };
		try
		{
			Parallel.ForEach(WhileTrue(), options, i =>
			{
				while (!options.CancellationToken.IsCancellationRequested)
				{
					UpdateLog("Sleeping in Cancellation Sample at " + i);
					Thread.Sleep(ran.Next(1000));
				}
			});
		}
		catch (OperationCanceledException)
		{
			UpdateLog("Operation has been cancelled."); ;
		}

		s.Stop();
		RunTime = s.Elapsed;
	}

	public static IEnumerable<int> WhileTrue()
	{
		for (int i = 0; ; i++)
			yield return i;
	}
}

Above you can see the CancellationTokenSource we’re using but as I said I’ll go into further detail on the next post. For the most part this is virtually identical to the other samples provided. There are two new properties exclusive to this class, the IsRunning property that defines when the run method is started and ended and the CancellationTokenSource itself.

CancellationSampleViewModel

public class CancellationSampleViewModel : SampleViewModel
{
	public CancellationSampleViewModel(CancellationSample Sample) : base(Sample) { }
	
	public void CancelRun()
	{
		((CancellationSample)Sample).CancellationTokenSource.Cancel();
	}
}

The view model for the CancellationSample couldn’t be much easier. As you know, view models act as a go-between from the model and the view. In this case we expose a method to cancel a run where we just call the cancel method on the CancellationTokenSource.

CancellationSampleView.xaml

<UserControl x:Class="TPLSamples.Views.CancellationSampleView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <BooleanToVisibilityConverter x:Key="BoolToVis" />
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <CheckBox Grid.Row="0" IsChecked="{Binding Sample.IsEnabled, Mode=TwoWay}" Content="{Binding Sample.SampleName}" ToolTip="{Binding Sample.SampleName}" />
        <Button Grid.Row="1" Margin="5" Content="Cancel Run" Visibility="{Binding Path=Sample.IsRunning, Converter={StaticResource BoolToVis}}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <ei:CallMethodAction TargetObject="{Binding}" MethodName="CancelRun" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
    </Grid>
</UserControl>

This view is similiar to the generic SampleView.xaml with the addition of the button for cancelling the run. There are a few points of note. The first is the Interaction.Triggers to fire on the button click event. We bind to the CancelRun method of the view model that was previously mentioned. This is just like how the Submit/Reset buttons are set up. Next is the BooleanToVisibilityConverter in the resources of the control which is a standard class available in “System.Windows.Controls”. We use that to set the visibility of the button based on when the sample is running. As with the other view we bind directly to the model property, in this case “IsRunning”. As mentioned in a previous post this isn’t the truest of MVVM as these properties should be exposed in the ViewModel. This is problematic as it introduces a dependency directly between your view and model. In this instance, however, the code is a lot cleaner to understand when we just bind directly to the model and I feel justified in using this way.

As with nearly all MVVM implementations the code-behind is just the boiler-plate code created for us.

Finally I need to discuss what I had to change to the code to support the new model. In the SamplerViewModel I added it as I’ve added other models and view models. What had to really change is the ItemsControl.

SamplerView.xaml ItemsControl

<ItemsControl Grid.Row="1" IsTabStop="False" ItemsSource="{Binding Samples}">
	<ItemsControl.ItemsPanel>
		<ItemsPanelTemplate>
			<WrapPanel Orientation="Horizontal" IsItemsHost="True" Utility:MarginUtilities.Margin="5" />
		</ItemsPanelTemplate>
	</ItemsControl.ItemsPanel>
	<ItemsControl.Resources>
		<DataTemplate DataType="{x:Type ViewModels:CancellationSampleViewModel}">
			<Views:CancellationSampleView DataContext="{Binding}" />
		</DataTemplate>
		<DataTemplate DataType="{x:Type ViewModels:SampleViewModel}">
			<Views:SampleView DataContext="{Binding}" />
		</DataTemplate>
	</ItemsControl.Resources>
	<!--<ItemsControl.ItemTemplate>
		<DataTemplate>
			<Views:SampleView DataContext="{Binding}" />
		</DataTemplate>
	</ItemsControl.ItemTemplate>-->
</ItemsControl>

I left commented out the ItemTemplate for the SampleView. Since we were originally only using just the base SampleView this made since. Now that we’ve expanded the Views possible we need to provide a mapping of the view models to the correct view as we’re going to do this in the resources. You can see all we’re doing is defining for each view model a view. The order is not important and the binding will take care of assigning the most restrictive type possible. So even if we flip the two data templates the final binding works correctly.

That’s all for now. As I mentioned in next week’s post I’ll go into detail on the sample and what it’s doing.

Thanks,
Brian

Leave a Reply