Infragistics WPF controls

Introducing System.Threading.Tasks namespace. Exploring Action<>, Task<>, and the BusyIndicator.

Introduction

The Microsoft .NET framework class library has provided many classes to facilitate complex programming tasks.  Topics like I/O, database management, and sockets were once incredibly tough to implement in languages like C, C++ and VB6.  When .NET was released, it brought with it many classes that abstracted these complex concepts and allowed the developer to concentrate on the business task at hand.  Multithreading is a way of providing asynchronous code execution and is an advanced concept that the framework has greatly simplified. 

The System.Threading namespace

The System.Threading namespace contains classes to abstract the complexities of multithreaded programming away from the developer.  Multithreading allows you to spawn worker threads to accomplish a unit of code execution.  System.Threading includes classes for synchronizing thread activities as well as accessing shared data .  These classes include; Mutex, Monitor, Interlocked, AutoResetEvent and more.  

At the heart of the System.Threading namespace is the System.Threading.Thread class.  This class represents an asynchronous thread of code execution.  The Thread class accepts a delegate to specify a method that returns void and is to be executed independently in regards to the thread that spawned it.  This was one of the first methods of asynchronous programming that was offered to .NET developers.

Here's an example

using System;

using System.Threading;



namespace AnsycThread

{

    class Program

    {

        delegate void LongRunningComputation();



        private static void LongRunningMethod()

        {

            //Add a long running computation here

            for(var loopCounter=0; loopCounter < 100; loopCounter++)

            {

                Console.WriteLine(string.Format("The value = {0} ", loopCounter));

            }



            Console.ReadKey();

        }



        public static void Main(string[] args)

        {

            LongRunningComputation codeBlock = LongRunningMethod;

            Thread spawnedWorker = new Thread(new ThreadStart(codeBlock));

            

            spawnedWorker.Start();

            Console.WriteLine("The thread has started.");

            Console.ReadKey();

        }

    }

}

This code is not very complicated, especially when compared to the same take written in a language like C or C++.  The System.Threading namespace has been around since the first release of the .NET framework.

Enter the System.Threading.Tasks.Task class

With .NET 4 came the System.Threading.Tasks namespace.  The idea of a Thread is a low level concept.  To think in terms of processes, threads, and thread management is to think at the level a computers processor.  The Task class was created to abstract these machine level ideas away from the developer so that we could achieve the same solutions that multithreading offers but from the viewpoint of task and workflows instead of threads and processors.  Much like the System.Threading.Thread class, the Task class is simply an asynchronous operation represented by a delegate.  However, there are some key differences.  For instance, there is a generic Taks<T> where T represents the return type of the delegate.  The Task class also handles a lot of the manual aspects of multithreaded programming such as picking a thread from the thread pool, as well as optimization with multicore/multiprocessor systems.  The System.Threading.Tasks namespace event offers functionality to support parallelism, but I'm getting ahead of myself. We are going to examine an example that makes use of the Task class as well as the System.Action class.

The System.Action class represents a delegate that takes no arguments and returns void.  There is a generic version of the Action class as well.  It greatly simplifies the syntax of delegates.

Consider the following example which is essentially the same as the prior example, except this example uses the Task and Action classes instead of Thread and delegate.

namespace AnsycTask

{

    class Program

    {

        private static void LongRunningMethod()

        {

            //Add a long running computation here

            for(var loopCounter=0; loopCounter < 100; loopCounter++)

            {

                Console.WriteLine(string.Format("The value = {0} ", loopCounter));

            }

            Console.WriteLine("Press a key");

            Console.ReadKey();

        }



        public static void Main(string[] args)

        {

            Action codeBlock = LongRunningMethod;

            var spawnedWorker = new Task(codeBlock);

            

            spawnedWorker.Start();

            Console.WriteLine("The thread has started.");

            Console.ReadKey();

        }

    }

}

Accessing the UI from another thread using Dispatcher.BeginInvoke

As you can see, multithreaded programming and allow you to complete multiple computing tasks simultaneously.  One of the draw backs of this approach is the fact that you can't directly access controls that were instantiated on the UI thread from your Tasks or Threads.  This is where the System.Windows.Dispatcher class can help.  When you call the BeginInvoke method of the Dispatcher class, it allows you to execute an asynchronous delegate on the same thread that the Dispatcher is associated.  I've included an example that uses the Task, Action, and Dispatcher classes in a WPF application.  

This example will execute a Task when the user clicks a button.  The task will open a text file and read the contents of the file.  We use the ContinueWith method of the Task object to specify another Task to run when the first task has completed.  In the ContinueWith Task, we will use the Dispatcher class to set the IsBusy property on a BusyIndicator object.  The BusyIndicator class comes from the WPF Extended toolkit.  

Here's the example..

<Window x:Class="FileReadBusyIndicator.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:extToolkit="clr-namespace:Xceed.Wpf.Toolkit;assembly=WPFToolkit.Extended"

        Title="MainWindow" Height="350" Width="525">

    <extToolkit:BusyIndicator x:Name="busyIndicator">

    <Grid>

        <Grid.RowDefinitions>

            <RowDefinition Height="78*" />

            <RowDefinition Height="78*" />

            <RowDefinition Height="106*" />

            <RowDefinition Height="50*" />

            </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>

            <ColumnDefinition />

            <ColumnDefinition />

        </Grid.ColumnDefinitions>

            <Button Content="Load File" Grid.Column="1" Grid.Row="3" Height="23" HorizontalAlignment="Left" Margin="118,9,0,0" Name="btnLoadFile" VerticalAlignment="Top" Width="75" Click="btnLoadFile_Click" />

            <TextBlock Height="45" HorizontalAlignment="Left" Margin="32,15,0,0" Name="txtDescription" Text="Click the button below to asynchrnously load the contents of the text file specified in the textbox." VerticalAlignment="Top" Grid.ColumnSpan="2" Width="390" />

            <TextBlock x:Name="txtContents" Grid.Row="1" ScrollViewer.CanContentScroll="True" Height="183" HorizontalAlignment="Left" Margin="6,0,0,0" Text="rwaraadsadsdsasdafsdfsdafsdaf" VerticalAlignment="Top" Width="491" Grid.ColumnSpan="2" Grid.RowSpan="2">

            </TextBlock>

        </Grid>

    </extToolkit:BusyIndicator>

</Window>

And for the codebehind

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

using System.Threading.Tasks;

using Microsoft.Practices.Unity;

using System.IO;

using System.ComponentModel;

using System.Threading;

using System.Windows.Threading;

using Xceed.Wpf.Toolkit;



namespace FileReadBusyIndicator

{

    /// <summary>

    /// Interaction logic for MainWindow.xaml

    /// </summary>

    public partial class MainWindow : Window

    {

        private void LongRunningMethod()

        {

            //Add a long running computation here

            for (var loopCounter = 0; loopCounter < 100; loopCounter++)

            {

                using (var streamReader = new StreamReader(@"c:\development\openme.txt"))

                {

                    var contents = streamReader.ReadToEnd();

                    Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate { this.txtContents.SetValue(TextBlock.TextProperty, contents); }, null);

                }

            }

        }



        public MainWindow()

        {

            InitializeComponent();

        }



        private void btnLoadFile_Click(object sender, RoutedEventArgs e)

        {

            busyIndicator.IsBusy = true;



            Action readFile = LongRunningMethod;

            var spawnedWorker = new Task(readFile);



            var completedTask = new Action<Task>((t) => { Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate { busyIndicator.SetValue(BusyIndicator.IsBusyProperty, false); }, null); });

            

            spawnedWorker.ContinueWith(completedTask);

            spawnedWorker.Start();

        }

    }

}

As you can see, the button's click event handler sets the BusyIndicator's IsBusy property to true which will show the ProgressBar.  Next the event handler will create a Task and set it to execute an action that points to a method to read the contents of a file.  The method will use the Dispatcher class to set the TextProperty dependency property of a textbox to the contents of the text file.  When the task completes its execution, it will proceed to execute the task specified in the ContinueWith method which uses the Dispatcher class to set the BusyIndicator's IsBusyProperty dependency property's value to false, causing the ProgressBar to be hidden.

You can download the source to the example projects here AsyncTaskExample.zip (352.19 kb).

This concludes my introduction to the System.Threading.Tasks namespace.

Thanks for reading.

~/Buddy James

 

kick it on DotNetKicks.com

 

 



Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

About the author

My name is Buddy James.  I'm a Microsoft Certified Solutions Developer from the Nashville, TN area.  I'm a Software Engineer, an author, a blogger (http://www.refactorthis.net), a mentor, a thought leader, a technologist, a data scientist, and a husband.  I enjoy working with design patterns, data mining, c#, WPF, Silverlight, WinRT, XAML, ASP.NET, python, CouchDB, RavenDB, Hadoop, Android(MonoDroid), iOS (MonoTouch), and Machine Learning. I love technology and I love to develop software, collect data, analyze the data, and learn from the data.  When I'm not coding,  I'm determined to make a difference in the world by using data and machine learning techniques. (follow me at @budbjames).  

Related links

Month List