Posts tagged ‘wpf’

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.

BackgroundWorker simple test

So. I wanted to try the BackgroundWorker since it’s been several years since I used it.

I also happened to be a good project for a first simple blog post. If I ever churn out a third post, remains to be seen.

As for the code, it is as simple as it can be. But if anyone can benefit anything from seeing this, it’s all good.

public partial class MainWindow : Window
    {
        private BackgroundWorker worker;

        public MainWindow()
        {
            InitializeComponent();

            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(OnWorkerDoWork);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnWorkerCompleted);
            worker.ProgressChanged += new ProgressChangedEventHandler(OnWorkerProgressChanged);
            worker.WorkerReportsProgress = true;
            progressBar.Minimum = 0;
            progressBar.Maximum = int.MaxValue;
        }

        private void OnWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            textBlock.Text = "Complete";
        }

        private void OnButtonClick(object sender, RoutedEventArgs e)
        {
            if (!worker.IsBusy)
            {
                worker.RunWorkerAsync();
            }
        }

        private void OnWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            textBlock.Text = e.ProgressPercentage.ToString();
            progressBar.Value = e.ProgressPercentage;
        }

        private void OnWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            int check = int.MaxValue / 100;
            for (int i = 1; i < int.MaxValue; i++)
            {
                if (i % check == 0)
                {
                    worker.ReportProgress(i);
                }
            }
        }
    }

And the XAML:

<Window 
    x:Class="BackgroundWorkerTest.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="MainWindow" 
    Height="350" Width="525">
    <StackPanel>
        <TextBlock 
            x:Name="textBlock" 
            Text="" 
            HorizontalAlignment="Center" 
            VerticalAlignment="Center" 
            FontSize="20" 
            FontWeight="Bold"/>
        <Button 
            Content="Start" 
            Height="23" 
            Name="startButton" 
            Width="75" 
            Click="OnButtonClick"/>
        <ProgressBar 
            x:Name="progressBar" 
            Height="30"/>
    </StackPanel>
</Window>