BackgroundWorker test, this time with Task

Ok, so naturally I don’t want to be old school and do long time running operations with BackgroundWorker. So I wanted to try to clean up the previous code and rewrite it using Task (new in .NET4) instead of BackgroundWorker.

I wanted to recreate the previous example, with the addition of possibility to run multiple tasks at the same time, without having the UI freeze.

This is what I came up with (setup in MVVM fashion).

ViewModel:

public class NumberCounterTaskViewModel : ViewModelBase
{
    private Task<TimeSpan> task;

    private string infoText;
    private double currentProgressValue;

    public NumberCounterTaskViewModel()
    {
        SetupTask();
    }

    private void SetupTask()
    {
        CurrentProgressValue = 0;

        //  Create a delegate to be invoked in the long time running
        //  service method.
        Action<int> progressUpdatedCallback = i => SetProgress(i);

        //  Creates a new task which will hold a TimeSpan as 
        //  Result property. ALongRunningMethod returns TimeSpan value
        task = new Task<TimeSpan>(() => PseudoService.ALongRunningMethod(progressUpdatedCallback));

        //  When the long running task is finished, we 
        //  want to display the result
        task.ContinueWith(t => SetCompletedTime(t.Result));
    }

    private void SetCompletedTime(TimeSpan ts)
    {
        InvokeOnUIThread(() =>
            {
                InfoText = string.Format("Completed in {0}.{1} sec",
                    ts.Seconds.ToString(),
                    ts.Milliseconds.ToString());
                OnPropertyChanged("StartCommand");
            });
    }

    private void SetProgress(int progress)
    {
        InvokeOnUIThread(() =>
        {
            InfoText = progress.ToString();
            CurrentProgressValue = (double)progress;
        });
    }

    /// <summary>
    /// Property for exposing value to View
    /// </summary>
    public string InfoText
    {
        get { return infoText; }
        set
        {
            if (infoText != value)
            {
                infoText = value;
                OnPropertyChanged("InfoText");
            }
        }
    }

    /// <summary>
    /// Property for exposing value to View
    /// </summary>
    public double CurrentProgressValue
    {
        get { return currentProgressValue; }
        set
        {
            if (currentProgressValue != value)
            {
                currentProgressValue = value;
                OnPropertyChanged("CurrentProgressValue");
            }
        }
    }

    /// <summary>
    /// ICommand for binding the start of task to a button
    /// </summary>
    public ICommand StartCommand
    {
        get
        {
            return new DelegateCommand(
                () => StartCommandExecute(),
                () => StartCommandCanExecute());
        }
    }

    /// <summary>
    /// Execute method for StartCommand.
    /// </summary>
    private void StartCommandExecute()
    {
        //  If we've run the operation once, we reset the task
        if (task.IsCompleted)
            SetupTask();
        task.Start();
    }

    /// <summary>
    /// Defines whether StartCommand can be executed. 
    /// Task cannot be started if its alread running
    /// </summary>
    /// <returns></returns>
    private bool StartCommandCanExecute()
    {
        return task.Status != TaskStatus.Running;
    }

    /// <summary>
    /// Invokes an action on the UI-thread. Needed since 
    /// we do work in the background on another thread
    /// </summary>
    /// <param name="action">Delegate to be invoked on UI thread</param>
    private void InvokeOnUIThread(Action action)
    {
        if (Application.Current != null)
        {
            Application.Current.Dispatcher.BeginInvoke(action);
        }
    }
}

and

public class PseudoService
{
    /// <summary>
    /// Very important and long time running stuff that happens in the background
    /// </summary>
    /// <returns></returns>
    public static TimeSpan ALongRunningMethod(Action<int> progressCallback)
    {
        DateTime before = DateTime.Now;
        int check = int.MaxValue / 100;
        for (int i = 1; i < int.MaxValue; i++)
        {
            if (i % check == 0)
            {
                progressCallback(i);
            }
        }
        return new TimeSpan(DateTime.Now.Ticks - before.Ticks);
    }
}

View:

<UserControl x:Class="TaskTest.NumberCounterTaskView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             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" 
             xmlns:viewModel="clr-namespace:TaskTest"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             d:DataContext="{d:DesignInstance Type={x:Type viewModel:NumberCounterTaskViewModel}}"
             >
    <UserControl.DataContext>
        <viewModel:NumberCounterTaskViewModel />
    </UserControl.DataContext>
    <Border Background="#E7F7F9" BorderBrush="#A3BDED" 
            CornerRadius="3" BorderThickness="4" Margin="2">
    <DockPanel LastChildFill="True">
        <TextBlock
            DockPanel.Dock="Bottom"
            x:Name="textBlock"
            Text="{Binding Path=InfoText}"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Center"
            FontSize="20"
            
            FontWeight="Bold"/>
        <Button
            DockPanel.Dock="Left"
            Command="{Binding Path=StartCommand}"
            Content="Start"
            Height="23"
            Width="75"
            Margin="2"
            x:Name="startButton"
            />
        
        <ProgressBar
            DockPanel.Dock="Right"
            Value="{Binding CurrentProgressValue}"
            x:Name="progressBar"
            Height="23"
            Margin="2"/>
    </DockPanel>
    </Border>
</UserControl>

Added three of these to a stackpanel in a window:

<Window x:Class="TaskTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow"
        xmlns:local="clr-namespace:TaskTest"
    Height="350" Width="525">
    <StackPanel>
        <local:NumberCounterTaskView />
        <local:NumberCounterTaskView />
        <local:NumberCounterTaskView />
    </StackPanel>
</Window>

And you get this:

Three controls that runs a long time running operation in the background while the rest of the UI is active and responsive.

Leave a Reply

You must be logged in to post a comment.