Question

A few days ago, I started using tasks in my WPF application for parallelization purposes. My application needs to perform some work every 5 seconds. This work has to be parallelized by 4 tasks. And in addition to that, the background worker needs to be implemented in order to avoid freezing of the UI while doing the work by tasks. I found lots of examples to understand how tasks work. However, I couldn't find any simple examples to understand how tasks work along with timers, background workers and locks of course. I wrote a simple example based on my understanding. Please, give me advices as to whether I am doing it correctly. This way, I will have a better understanding of multitasking in WPF. I'm looking forward to your replies.

       namespace timer_and_thread
            {
                /// <summary>
                /// Interaction logic for MainWindow.xaml
                /// </summary>
                public partial class MainWindow : Window
                {
                    DispatcherTimer TimerObject;
                    Task[] tasks;
                    readonly object _countLock = new object();
                    int[] Summ = new int[10];
                    int Index = 0;
                    int ThreadsCounter = 0;


                    public MainWindow()
                    {
                        InitializeComponent();
                        TimerObject = new DispatcherTimer();
                        TimerObject.Tick += new EventHandler(timer_Elapsed);
                        TimerObject.Interval = new TimeSpan(0, 0, 5);
                    }


                    // call the method every 5 seconds
                    private void timer_Elapsed(object sender, EventArgs e)
                    {
                        TimerObject.Stop();

                        // 4 - is the tasks' count
                        ThreadsCounter = 4;
                        Index = 0;

                        tasks = new Task[4];
                        tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork());
                        tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork());
                        tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork());
                        tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork());

                         // wait untill all tasks complete
                         while (ThreadsCounter != 0) ;


                         TimerObject.Start();

                    }


                    private void DoSomeLongWork()
                    {
                        while (Index < Summ.Length)
                        {


                            // lock the global variable from accessing by multiple threads at a time
                        int localIndex = Interlocked.Increment(ref Index) - 1;

                            //I wrote rundom number generation just a an example of doing some calculation and getting some result. It can also be some long calculation. 
                            Random rnd = new Random();
                            int someResult = rnd.Next(1, 100000);

                            // lock the global variable (Summ) to give it the result of calculation 
                            lock (_countLock)
                            {
                                Summ[localIndex] = someResult;
                            }      
                        }


                        Interlocked.Decrement(ref ThreadsCounter);
                        return;

                    }

                    // button by which I start the application working
                    private void Start_Button_Click_1(object sender, RoutedEventArgs e)
                    {
                        TimerObject.Start();
                    }

                }
            }

I have 2 additional questions:

  1. Can I use a task instead of background worker?

  2. As I have it, locking is used in order to prevent accessing a global variable by a thread. However, to access UI elements by threads, I should use Dispatcher. Right?

Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
    {

label1.Content = "Some text";
}));
Was it helpful?

Solution

It looks to me as though you don't even need the BackgroundWorker. Currently you have:

private void timer_Elapsed(object sender, EventArgs e)
{
    TimerObject.Stop();

    BackgroundWorker backgroundWorkerObject = new BackgroundWorker();
    backgroundWorkerObject.DoWork += new DoWorkEventHandler(StartThreads);
    backgroundWorkerObject.RunWorkerAsync();

    TimerObject.Start();
}


private void StartThreads(object sender, DoWorkEventArgs e)
{
    tasks = new Task[4];
    tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork());

    // Give the tasks a second to start.
    Thread.Sleep(1000);
}

All the BackgroundWorker does is start threads and then for some unknown reason wait a second before continuing. I don't see any particular reason why you'd wait a second before letting the BackgroundWorker exit.

If you remove that unnecessary delay, then you can change your code to:

private void timer_Elapsed(object sender, EventArgs e)
{
    TimerObject.Stop();

    tasks = new Task[4];
    tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork());

    TimerObject.Start();
}

Another problem you have is that if those tasks don't complete before the timer elapsed event occurs again, you're going to overwrite the Tasks array and your calculations (the Sum array, in particular) will contain information from both sets of calculations. Your use of global variables here is going to get you in trouble if those threads take longer than five seconds to complete.

I'm going to assume that your DoSomeLongWork method is just a placeholder, and won't critique that. I will mention, though, that there's no need to lock in order to increment a shared variable. Rather than:

int localIndex;
// lock the global variable from accessing by multiple threads at a time
lock (_countLock)
{
    localIndex = Index;
    Index++;
}

You can write:

localIndex = Interlocked.Increment(ref Index, 1) - 1;

Interlocked.Increment increments the value atomically and returns the new value.

OTHER TIPS

To answer the question from Aug 30th, you could look at the ContinueWhenAll function for Tasks instead of the while loop and move the start of the timer to that. It takes a list of tasks, then when all of them has completed, creates another task and runs that. This should keep your UI from freezing, although if it HAS to run every 5 seconds, rather than every 5 second after the completion of the previous tasks, you may need to look into something different.

http://msdn.microsoft.com/en-us/library/dd321473%28v=vs.110%29.aspx

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top