Blog Archives

Tasks (basics)

A task is like a small add-on for thread pools. In fact the standard task scheduler uses the same thread pool as System.Threading.ThreadPool.
Tasks do return values, threads don’t. That is the big difference. The instance of a task class can tell you if the work is completed and what the result is. As the task scheduler uses the standard thread pool, its limitations are in the range of this pool. Therefore to adjust the behaviour of tasks you have to adjust the standard thread pool (unless you use a custom thread pool or task manager). The task manager does not start additional threads when needed, instead it waits for the pool to assign the next available thread.

static void task1() {
  Task t = new Task(() =>
    Thread.Sleep(1000);
    Console.WriteLine("hello task!");
  });

  t.Start();
  t.Wait();
  Console.WriteLine("press enter to exit");
  Console.ReadLine();
}

Calling t.Wait() is equivalent to calling Thread.Join().

static void task2() {
  Task<int> t = new Task<int>(() => {
    Thread.Sleep(1000);
    Console.WriteLine("hello task!");
    return Thread.CurrentThread.ManagedThreadId;
  });
  t.Start();
  //t.Wait();
  Console.WriteLine("task result is: " + t.Result); // implicit wait
  Console.ReadLine();
}

But when you attempt to read the t.Result the current thread will wait until the result of the task is available.
Therefore t.Wait() is commented out in method task2().

Let’s add a continuation task now. This means that we wait for the first task to finish and then start another task.

static void task3() {
  Task<int> t = new Task<int>(() => {
    Thread.Sleep(5000);
    Console.WriteLine("hello task!");
    return 1;
  });
  t.Start();
  Console.WriteLine(DateTime.Now.ToString("HH:mm:ss"));
  Task<int> t2 = t.ContinueWith((i) => {
    return i.Result + 1;
  });
  Console.WriteLine(DateTime.Now.ToString("HH:mm:ss"));
  Console.WriteLine("task result is: " + t2.Result); // implicit wait
  Console.WriteLine(DateTime.Now.ToString("HH:mm:ss"));
  Console.ReadLine();
}

The output could look like:
14:33:31
14:33:31
hello task!
task result is: 2
14:33:36

The point that I am trying to make here is that the t.ContinueWith instruction does not stop the current thread. This way you can construct a chain of commands that are executed in the future. The t2.Result then implicitly waits for the first and second task to finish.

The next example demonstrates the behavior of ContinueWith when an exception is thrown during the task execution. The code is self-explanatory. Comment the “throw new Exception();” out and see what changes.

static void task4() {
  Task<int> t = new Task<int>(() => {
    throw new Exception();
    return 1;
  });

  t.Start();
  t.ContinueWith((i) => { Console.WriteLine("Task Canceled"); }, TaskContinuationOptions.OnlyOnCanceled);
  t.ContinueWith((i) => { Console.WriteLine("Task Faulted"); }, TaskContinuationOptions.OnlyOnFaulted);
  var tasks = t.ContinueWith((i) => { Console.WriteLine("Task Completion"); }, TaskContinuationOptions.OnlyOnRanToCompletion);
  tasks.Wait();
} //

So far we were creating task instances by using the “new” keyword. There is another option. You can also create a task factory. This makes repetitive task creations easier. I am also adding child tasks to speed up a little bit.

static void task5() {
  Task<int> parent = new Task<int>(() => {
    int[] results = new int[4];
    TaskFactory factory = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.ExecuteSynchronously);
    factory.StartNew(() => { results[0] = 0; Thread.Sleep(1000); Console.WriteLine("executing sub task 0"); });
    factory.StartNew(() => { results[1] = 1; Thread.Sleep(500); Console.WriteLine("executing sub task 1"); });
    factory.StartNew(() => { results[2] = 2; Thread.Sleep(1000); Console.WriteLine("executing sub task 2"); });
    factory.StartNew(() => { results[3] = 3; Thread.Sleep(750); Console.WriteLine("executing sub task 3"); });
    return results;
  });

  parent.Start();
  Task t = parent.ContinueWith(x => {
    for (int i = 0; i < 4; i++) {
      Console.WriteLine("result of task " + i + " = " + parent.Result[i].ToString());
    }
  });

  t.Wait();
  Console.ReadLine();
} //
Advertisements

Thread Pools (basics)

When you deal with many threads you should consider thread pools. Creating threads costs some time and resources. And even worse, you cannot reuse a thread.

Thread pool threads stay alive. The purpose is to use them again. They are waiting in a queue to be assigned new jobs. You do not have to instantiate a new thread each time you have a new job. Overall it reduces the number of threads. And in fact it does not help you a lot to have 100 threads running on a four processor machine. The system might even slow down, because it performs a lot of context switching.

Thread pools can automatically manage the amount of threads.

static void hello(object o) {
  for (int i = 0; i < 1000; i++) {
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " line " + i + " " + o as string);
    Thread.Sleep(2);    // let's wait 2 milliseconds to get some interaction between the two threads
  }
} //

static void Main(string[] args) {
  ThreadPool.QueueUserWorkItem(hello, "SomeFunnyParameter");
  ThreadPool.QueueUserWorkItem(hello);
  Console.ReadLine();
} //