Thursday, 4 May 2017

TPL 09 Task Factory

We've leveraged the TaskFactory in numerous posts before but lets' investigate it a little bit more, The The Task factory is a class that's purpose is to create and support tasks. First let's take a look at a simple task creation.

using System;
using System.Threading.Tasks;

namespace pc.tplTaskFactory
{
    class Program
    {
        static void Main(string[] args)
        {
            Task.Factory.StartNew(()=> {
                Task.WaitAll(Task.Delay(3000));
                Console.WriteLine("Lambda task completed");
            });

            Task.Factory.StartNew(delegate () {
                Task.WaitAll(Task.Delay(2000));
                Console.WriteLine("anonymous task completed");
            });

            Task.Factory.StartNew(NamedTask);

            Console.ReadKey();
        }

        static void NamedTask()
        {
            Task.WaitAll(Task.Delay(1000));
            Console.WriteLine("Named task completed");
        }
    }
}

now The taskfactory.StartNew returns a reference to task, a reference that can then be leveraged as shown below

using System;
using System.Threading.Tasks;

namespace pc.tplTaskFactory
{
    class Program
    {
        static void Main(string[] args)
        {
            var t1 = Task.Factory.StartNew(() =>
            {
                Task.WaitAll(Task.Delay(3000));
                Console.WriteLine("Lambda task completed");
            });

            var t2 = Task.Factory.StartNew(delegate ()
            {
                Task.WaitAll(Task.Delay(2000));
                Console.WriteLine("anonymous task completed");
            });

            var t3 = Task.Factory.StartNew(NamedTask);

            var myTasks = new Task[] { t1,t2,t3 };
            var t4 = Task.Factory.ContinueWhenAll(myTasks, tasks => {
                Console.WriteLine("all tasks have completed");
            });

            Task.WaitAll(t4);
        }

        static void NamedTask()
        {
            Task.WaitAll(Task.Delay(1000));
            Console.WriteLine("Named task completed");
        }
    }
}

what we've done is leverage a continueWhenAll task to accomplish something once all of our "Feeder tasks" are complete, we can also leverage the Task class to create a wait all or wait any point in our logic.

Our TaskFactory can also take in a parameter in the form of an object, that could just be a class or a boxed struct containing multiple parameters inside of it.

using System;
using System.Threading.Tasks;

namespace pc.tplTaskFactory
{
    class Program
    {
        static void Main(string[] args)
        {
            var t1 = Task.Factory.StartNew(o =>
            {
                Task.WaitAll(Task.Delay(int.Parse(o.ToString())));
                Console.WriteLine("Lambda task completed");
            },3000);

            var t2 = Task.Factory.StartNew(delegate (object o)
            {
                Task.WaitAll(Task.Delay(int.Parse(o.ToString())));
                Console.WriteLine("anonymous task completed");
            }, 2000);

            var t3 = Task.Factory.StartNew(NamedTask, 1000);

            var myTasks = new Task[] { t1,t2,t3 };
            var t4 = Task.Factory.ContinueWhenAll(myTasks, tasks => {
                Console.WriteLine("all tasks have completed");
            });

           
            Task.WaitAll(t4);
        }

        static void NamedTask(object o)
        {
            Task.WaitAll(Task.Delay(int.Parse(o.ToString())));
            Console.WriteLine("Named task completed");
        }
    }
}

The tasks created by our factory can also have return types like normal tasks and those return types can be leveraged in the continueWhenAll or continueWhenAny functions are called

using System;
using System.Threading.Tasks;

namespace pc.tplTaskFactory
{
    class Program
    {
        static void Main(string[] args)
        {
            var t1 = Task.Factory.StartNew<string>(o =>
            {
                Task.WaitAll(Task.Delay(int.Parse(o.ToString())));
                return $"Lambda task completed in {o} seconds";
            },3000);

            var t2 = Task.Factory.StartNew<string>(delegate (object o)
            {
                Task.WaitAll(Task.Delay(int.Parse(o.ToString())));
                return $"anonymous task completed in {o} seconds";
               
            }, 2000);

            var t3 = Task.Factory.StartNew<string>(NamedTask, 1000);
           
            var myTasks = new Task<string>[] { t1,t2,t3 };
            var t4 = Task.Factory.ContinueWhenAll(myTasks, tasks => {
                foreach(Task<string> t in tasks)
                    Console.WriteLine(t.Result);

                Console.WriteLine("All tasks are complete");
            });

           
            Task.WaitAll(t4);
        }

        static string NamedTask(object o)
        {
            Task.WaitAll(Task.Delay(int.Parse(o.ToString())));
            return $"Named task completed in {o} seconds";
        }
    }
}

so above we create 3 tasks that generate a return type then we use all three return types in our continue when all method.

we can even use Cancellation tokens inside of our task factory

using System;
using System.Threading;
using System.Threading.Tasks;

namespace pc.tplTaskFactory
{
    class Program
    {
        static void Main(string[] args)
        {
            var ctSource = new CancellationTokenSource();
            var ct = ctSource.Token;

            var t1 = Task.Factory.StartNew<string>(o =>
            {
                Task.WaitAll(Task.Delay(int.Parse(o.ToString())));
                if (ct.IsCancellationRequested)
                    return "Lambda task was cancelled";
                return $"Lambda task completed in {o} seconds";
            }, 3000, ct);

            var t2 = Task.Factory.StartNew<string>(delegate (object o)
            {
                Task.WaitAll(Task.Delay(int.Parse(o.ToString())));
                if (ct.IsCancellationRequested)
                    return "anonymous task was cancelled";
                return $"anonymous task completed in {o} seconds";

            }, 2000, ct);

            var t3 = Task.Factory.StartNew<string>(NamedTask, 1000);

            var myTasks = new Task<string>[] { t1, t2, t3 };
            var t4 = Task.Factory.ContinueWhenAll(myTasks, tasks =>
            {
                foreach (Task<string> t in tasks)
                    Console.WriteLine(t.Result);

                Console.WriteLine("All tasks are complete");
            });

            Console.WriteLine("any key to cancel");
            Console.ReadKey();
                ctSource.Cancel();
      

            Task.WaitAll(t4);
            Console.WriteLine("finished");
        }

        static string NamedTask(object o)
        {
          
            Task.WaitAll(Task.Delay(int.Parse(o.ToString())));
            return $"Named task completed in {o} seconds";
        }
    }
}


The Task factory can do everything regular tasks can do i just find it very useful that you don't have to explicitly call the Start method, that you can do it on one line