public static void Main(string[] args) { /* --------------------- 91) Retrieving data from Thread function using callback method -------------------- */ // Example // Please enter the target number // 5 // Sum of numbers is 15 // So // Main thread retreives the target number from the user // Main thread creates a child thread and pass the target number to the child thread. // The child thread computes the sum of numbers and then returns the sum to the Main // thread using callback functions. // The callback method prints the sum of numbers. // Step 1: Create a callback delegate. The actual callback method signature should // match with the signature of this delegate. // refer to MyNumber class in Program5.cs Console.WriteLine("Please enter the target number"); int target = Convert.ToInt32(Console.ReadLine()); // so just pass the name of the function SumOfNumbersCallback callback = new SumOfNumbersCallback(PrintSumOfNumbers); MyNumber number = new MyNumber(target, callback); Thread T1 = new Thread(new ThreadStart(number.PrintSumOfNumbers)); T1.Start(); /* --------------------- 92) Significance of Thread Join and Thread IsAlive functions -------------------- */ // Thread.Join and Thread.IsAlive functions // Join blocks the current thread and makes it wait until the thread // on which Join method is invoked completes. // Join method also has an overload where we can specify the timeout. // If we don't specify the timeout the calling thread waits indefinitely, // until the thread on which Join() is invoked completes. // This overloaded Join(int millisecondsTimeout) method returns a boolean // True if the thread has terminated otherwise false // Join is particularly useful when we need to wait and collect a result from // a thread execution or if we need to do some clean-up after the thread has completed. // IsAlive returns boolean. True if the thread is still executing otherwise false // View static function definitions at the end of the class Console.WriteLine("Main started"); // a worker thread Thread T2 = new Thread(Program6.Thread1Function); T2.Start(); Thread T3 = new Thread(Program6.Thread2Function); T3.Start(); Console.WriteLine("Main completed"); // The order of the output // will differ a bit every time it executes // The statements are not guarantee in any order // which is the nature of multithreaded programming. // for example the output COULD BE // Main started // Thread1Function started // Main completed // Thread2Function started // So the Main function, we get one thread for free // then the Main thread creates two additional threads, T2 and T3 // Once the Main thread creates the additional thread, // the Main thread will not wait for that thread to complete, // it will proceed to the next line // Main thread doesn't wait, it proceeds // Sometimes you may need to make the Main thread wait while // worker threads are doing their work // since you may want to collect the result from worker threads and // based on that we want the main thread to do something. // That's when Join is useful Console.WriteLine("Main started"); // a worker thread Thread T4 = new Thread(Program6.Thread1Function); T4.Start(); Thread T5 = new Thread(Program6.Thread2Function); T5.Start(); // once this line is executed, the Main thread is going to suspend // its execution and wait for T4 to return from its method // and once it has completed its exeuction T4.Join(); Console.WriteLine("Thread1Function completed"); // Main thread will wait until this thread completes its execution T5.Join(); Console.WriteLine("Thread2Function completed"); // SO now with the above code which uses .Join() // you will always get the same output // as we force the Main thread to wait. Console.WriteLine("Main completed"); // // There is another overload of the .Join() method where you can specify the timeout // you could pass the timeout in milliseconds for the time that you are willing to wait // until the thread completes its execution Thread T6 = new Thread(Program6.Thread1Function); if (T6.Join(1000)) { Console.WriteLine("Thread1Function completed!"); } else { Console.WriteLine("Thread1Function has not completed in 1 second"); } if (T6.IsAlive) { Console.WriteLine("Thread1Function is still processing"); } else { Console.WriteLine("Thread1Function has completed execution"); } // to check every 500 milliseconds for (int i = 1; i < 10; i++) { if (T6.IsAlive) { Console.WriteLine("Thread1Function is still processing"); Thread.Sleep(500); } else { Console.WriteLine("Thread1Function has completed execution"); break; } } /* --------------------- 93) Protecting shared resources from concurrent access -------------------- */ // What happens if shared resources are not protected from concurrent access // in multithreaded programs? // The output or behavior of the program can become inconsistent if the shared // resourcecs are not protected from concurrent access in multithreaded program. // This program is a single-threaded program. // In the Main() method, AddOneMillion() method is called 3 times // and it updates the total field correctly as expected // so far this program is single threaded, // where a single thread is used to execeute all of this code // and protecting shared resources is not a concern // and you always get the same output. AddOneMillion(); AddOneMillion(); AddOneMillion(); Console.WriteLine("Total = " + Total); // now rewrite the program to use multiple threads // Everytime we run the below program we get a different output. // The inconsistent output is because the Total field is a shared // resource is not protected from concurrent access by multiple threads. // The operator ++ is not thread safe. Thread thread1 = new Thread(Program6.AddOneMillion); Thread thread2 = new Thread(Program6.AddOneMillion); Thread thread3 = new Thread(Program6.AddOneMillion); thread1.Start(); thread2.Start(); thread3.Start(); thread1.Join(); thread2.Join(); thread3.Join(); Console.WriteLine("Total = " + Total); // And how to protect shared resources from concurrent access. // There are several ways to fix this. // using Interlocked.Increment() method // increments a specified variable and stores the result as an atomic operation // Interlocked.Increment(ref Total); // instead of // Total++; // Or with Locking: // shown below this class // so when we use lock, // only one thread at a time could enter that piece of code Stopwatch stopwatch = Stopwatch.StartNew(); Thread thread4 = new Thread(Program6.AddOneMillionLock); Thread thread5 = new Thread(Program6.AddOneMillionLock); Thread thread6 = new Thread(Program6.AddOneMillionLock); thread4.Start(); thread5.Start(); thread6.Start(); thread4.Join(); thread5.Join(); thread6.Join(); Console.WriteLine("Total = " + Total); stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedTicks); // A tick is a measurement of time, so // one millisecond contains 10,000 ticks Console.WriteLine(TimeSpan.TicksPerHour); // for example, it has other useful methods // Which option is better? // From a performance perspective, using Interlocked class is better // over using locking. // Locking locks out all the other threads except a single therad to read // and increment the Total variable. // This will ensure that the Total variable is updated safely. // The downside is that since all the other threads are locked out, // there is a performance hit. // The interlocked class can be used with addition/subtraction // (increment, decrement, add, etc.) on an int or long field. // The Interlocked class has methods for incrementing, // decrementing, adding, and reading variables atomincally. // Anything other than that we will wil have to resort to locking. /* --------------------- 94) Difference between Monitor and lock in C# -------------------- */ // Both monitor class and lock provides a mechanism that synchronizes access // to objects. // Lock is the shortcut for Monitor.Enter with try and finally. // see code below this class for example // They are equally valid. // for lock(_lock) // that code will acquire an exclusive lock // and the same is the case for Monitor.Enter // in C# 4, it's implemented slightly differently // as shown in function below this class // So in short, lock is a shortcut and it's the option for basic usage. // If you need more congtrol to implement advanced multithreading solutions // using TryEnter(), Wait(), Pulse(), and PulseAll() methods // then the Monitor class is your option /* --------------------- 95) Deadlock in a multithreaded program -------------------- */ // When a deadlock can occur. // Lets say we have 2 threads. // a) Thread 1 // b) Thread 2 // and 2 resources // a) Resource 1 // b) Resource 2 // Thread 1 has already acquired a lock on Resource 1 and wants to acquire a lock on resource 2. // At the same time Thread 2 has already acquired a lock on Resource 2 and wants to acquire a // lock on Resource 1. // Two threads never give up their locks, hence a deadlock. // They are waiting for each other. Console.WriteLine("Started"); var accountA = new Account(101, 5000); var accountB = new Account(102, 3000); AccountManager accountManagerA = new AccountManager(accountA, accountB, 1000); Thread t10 = new Thread(accountManagerA.Transfer); t10.Name = "t10"; AccountManager accountManagerB = new AccountManager(accountB, accountA, 2000); Thread t11 = new Thread(accountManagerB.Transfer); t11.Name = "t11"; t10.Start(); t11.Start(); t10.Join(); t11.Join(); Console.WriteLine("Main completed"); // The above code is going to deadlock!! // The Transfer methods are being executed on two different threads. // 2 different resources, and the lock is performed. // NOTE: Many modern computers have many processors // which enable them to execute in parallel. // Enable multiple processes as to make your applications // faster and more responsive. // A process is an instance of a program or an application. // So when you open/run an application, your operating system // loads that application within a process. // A process contains an image of that application's code. // Your operating system can execute many processes at the same time. // You could also have concurrency within each application // using threads. // A thread is a sequence of instructions. // A thread is "that thing which executes your code." /* --------------------- 96) How to resolve a deadlock in a multithreaded program -------------------- */ // There are several techniques to avoid and resolve deadlocks. // 1) Acquiring locks in a specific defined order // 2) Mutex class // 3) Monitor.TryEnter() method // In this video, will discuss, acquiring locks in a specific defined order // to resolve a deadlock. // We need to define a specific order. View the updated transfer method. // The possibility of having deadlocks is reduced. /* --------------------- 97) Performance of a multithreaded program -------------------- */ // Performance when run on a single core/processor machine // versus multi core/processor machine. // To find out how many processors you have on your machine. // 1) Using Task Manager // 2) Use the following code in any .NET application. // Environment.ProcessorCount // Console.WriteLine("Processor count " + Environment.ProcessorCount); // 3) On the windows command prompt window, type the following // echo %NUMBER_OF_PROCESSORS% // On a machine that has multiple processors. Multiple threads can execute // application code in parallel on different cores. For example, // if there are two threads and two cores, then each thread would run on an // individual core. This means, performance is better. // If two threads take 10 milli-seconds each to complete, then on a machine with // 2 processors, the total time taken is 10 milli-seconds. // On a machine taht has a single processor, multiple threads execute, one after // the other or wait until one thread finishes. // It is not possible for a single processor system to execute multiple threads // in parallel. Since the operating system switches between the threads so fast, // it just gives the illusion that the threads run in parallel. // On a single core/processor machine multiple threads can affect performance // negatively as there is overhead involved with context switching. // If two threads take 10 milli-seconds each to complete, then on a machine with 1 // processor, the total time taken is // 20 milli-seconds + (Thread context switching time, if any) // using System.Diagnostics; // StopWatch stopWatch = new StopWatch.StartNew(); // .... // stopWatch.Stop(); // Console.WriteLine("Total milliseconds without multiple threads"); /* --------------------- 98) Anonymous methods in C# -------------------- */ // An anonymous method is a method without a name. // They provide a way of creating delegate instances without having to // write a separate method. // Step 1: Create a method whose signature matches // with the signature of Predicate<Employee> delegate // private static bool FindEmplyee(Employee employee) // { // return employee.ID == 102; // } // Step 2: Create an instance of Predicate<Employee> delegate and pass the // method name as an argument to the delegate constructor. // Predicate<Employee> predicateEmployee = new Predicate<Employee>(FindEmployee); // Step 3: Now pass the delegate instance as the argument for Find() method // Employee employee = listEmployees.Find(XmlWriterTraceListener => predicateEmployee(x)); // Console.WriteLine("ID = {0}, Name {1}", employee.ID, employee.Name); // Anonymous method is being passed as an argument to the Find() method // This anonymous method replaces the need for step 1, 2, and 3 // employee = listEmployees.Find(delegate(Employee x) { return x.ID == 102; }); // Console.WriteLine("ID = {0}, Name {1}", employee.ID, employee.Name); // Also used for subscribing for an event handler. // private void Form1_Load(object sender, EventArgs e) // { // Button Button1 = new Button(); // Button1.Text = "Click Me"; // Button1.Click += new EventHandler(Button1_Click); // this.Controls.Add(Button1); // } // void Button1_Click(object sender, EventArgs e) // { // MessageBox.Show("Button Clicked"); // } // Anonymous Method // private void Form1_Load(object sender, EventArgs e) // { // Button Button1 = new Button(); // Button1.Text = "Click Me"; // Button1.Click += delegate(object obj, EventArgs eventArgs) // { // MessageBox.Show("Button Clicked"); // } // this.Controls.Add(Button1); // } /* --------------------- 99) Lambda expression in C# -------------------- */ // What are lambda expressions? // Anonymous methods and Lambda expressions are very similar. // Anonymous methods were introduced in C# 2 and Lambda expressions in C# 3. // To find an employee with Id == 102, using anonymous method. //Employee employee = listEmployees.Find(Employee => Employee.ID == 102); // You can also explicitly specify the Input but not required. // You can also explicitly specify the input type but this is not required. //Employee employee = listEmployees.Find((Employee Emp) => Emp.ID == 102); // int count = listEmployees.Count(x => x.Name.StartsWith("M")); // => is called lambda expression and reads as GOES TO // Notice that with a lambda expression you don't have to use the delegate // keyword explicitly. The parameter type is inferred. // Lambda expressions are more convenient to use than anonymous methods. // Lambda expressions are particularly helpful for writing LINQ query expressions.\ // In most of the cases, lambda expressions supersedes anonymous methods. // The only time you would prefer anonymous methods over lambdas, // is when we have to omit the parameter list when it's // not use within the body. // Anonymous methods allow the parameter list to be omitted entirely when it's not // used within the body, where as with lambda expressions this is not th ecase. // For example, with anonymous method, notice we have omitted the parameter list // as we are not using them within the body. // Button1.Clilck += delegate // { // MessageShow.Show("Button Clicked"); // }; // The above code can be rewritten using lambda expressions as show. // Notice that we cannot omit the parameter list. // Buttton1.Click += (eventSender, eventArgs) => // { // MessageBox.Show("Button Clicked"); // }; /* --------------------- 100) Func delegate in C# -------------------- */ // Purpose of Func<T, TResult> delegate in C# // In simple terms, Func<T, TResult> is just a generic delegate. // Depending on the requirement, the type parameters // can be replaced with the corresponding type arguments. // For example, // Func<Employee, string> is a delegate that represents a function // expecting Employee object as an input parameter and returns a string. // public class Employee // { // public int ID { get; set; } // public string Name { get; set; } // } // List<Employee> listEmployees = new List<Employee>() // { // new Employee{ ID = 101, Name = "Mark"}, // new Employee{ ID = 102, Name = "John"}, // new Employee{ ID = 103, Name = "Mary"} // }; // Func<Employee, string> selector = employee => "Name = " + employee.Name; // IEnumerable<string> names = listEmployees.Select(selector); // foreach (string name in names) // { // Console.WriteLine(name); // } // lambda expressions can also be used to achieve the same thing //IEnumerable<string> names = listEmployees.Select(employee => "Name = " + employee.Name); // What is the difference between the Func delegate and lambda expression? // They're the same, just two different ways to do the same thing. // The lambda syntax is newer, more concise, and easy way to write. // What if I have to pass two or more input parameters? // There are 17 overloaded versions of Func. // Enables you to write a modern version of a delegate. (function pointer) // as a reference to refer to a function (and its signature). /* --------------------- 101) Async and await in C# -------------------- */ // For retrieving some data from a dependency such as the network, // or disk, that is going to have some latency. // We don't want the thread to have to wait and reduce performance by blocking. // Should still be able to interact with the application. // To make it responsive. // Task class, can think of it as a verb. // Decorate with // private async void btnProcessFile(object sender, EventArgs e) // { // Task<int> task = new Task<int>(CountCharacters); // task.Start(); // so will execute the function asynchronously // int count = await task; // wait for the task to return, // /* --------------------- 102) C# wait for thread to finish without blocking -------------------- */ // So could implement it using Threads. // however it would creating blocking. // Have to wait for the worker thread to finish. // To make the main thread wait to finish we have to call // thread.Join(); // It will now perform as expected. // We have introduced a problem which is blocking the UI. // The thread that is in control should be modifying its properties, // no other thread should be doing that. // The right way is to use the begin invoke method. // Action delegate // Action action = () => { ...... } // this.BeginInvoke(action); // this ask the UI thread to execute asynchronusly }
public static void Main(string[] args) { /* --------------------- * 80) Some useful methods of List collection class * ---------------------- */ // TrueForAll() // Returns true or false depending on whether every element in the list matches the // conditions defined by the specified predicate // AsReadOnly() // Returns a read-only wrapper for the current collection. // Use this method if you don't want the client code to modify the collection // so no adding or removing from the collection // The ReadOnlyCollection will not have methods to add or remove items from the collection // you can only read items from this collection // TrimExcess() // Sets the capacity to the actual number of elements in the list, // if that number is less than a threshold value // NOTE: // This method can be used to minimize a collection's memory overhead if no new elements // will be added to the collection. The cost of reallocating and copying a large List<T> // can be considerable. So the TrimExcess method does nothing if the list is at more than 90 // percent of capacity. This avoids incurring a large reallocation cost for a relatively // small gain. The current threshold is 90 percent, but this could change in the future. List <NewCustomer> listCustomers = new List <NewCustomer>() { new NewCustomer() { ID = 1, Name = "Steven", Salary = 52000 }, new NewCustomer() { ID = 1, Name = "Joe", Salary = 65000 }, new NewCustomer() { ID = 1, Name = "Tony", Salary = 88000 } }; Console.WriteLine(listCustomers.TrueForAll(x => x.Salary > 5000)); ReadOnlyCollection <NewCustomer> readOnlyListCustomers = listCustomers.AsReadOnly(); foreach (NewCustomer customer in readOnlyListCustomers) { Console.WriteLine($"{customer.Name}"); } Console.WriteLine("First employee in the collection is {0}", readOnlyListCustomers[0].Name); Console.WriteLine("Capacity before trimming = " + listCustomers.Capacity); listCustomers.TrimExcess(); // So now this instance of List<> has a fixed size Console.WriteLine("Capacity after trimming = " + listCustomers.Capacity); // So the .TrimExcess() method is beneficial for saving memory if you know that you are not going // to be adding any new elements to the specific List<> instance /* --------------------- * 81) When to use a dictionary over list in C# * ---------------------- */ // Find() method of the List class loops through each object in the list until a match is found. // So if you want to lookup a value using a key dictionary is better for performance over list. // So use dictionary when you know the collection will be primarily used for lookups. Country country1 = new Country() { Code = "USA", Name = "UNITED STATES", Capital = "Washington D.C." }; Country country2 = new Country() { Code = "IND", Name = "INDIA", Capital = "New Delhi" }; Country country3 = new Country() { Code = "CAN", Name = "CANADA", Capital = "Ottawa" }; List <Country> listCountries = new List <Country>() { country1, country2, country3 }; string strUserChoice = string.Empty; do { Console.WriteLine("Please enter country code"); string strCountryCode = Console.ReadLine().ToUpper(); Country resultCountry = listCountries.Find(country => country.Code == strCountryCode); if (resultCountry == null) { Console.WriteLine("Country code not valid"); } else { Console.WriteLine("Name = {0}, Capital = {1}", resultCountry.Name, resultCountry.Capital); } do { Console.WriteLine("Do you want to continue? (Y or N)"); strUserChoice = Console.ReadLine().ToUpper(); } while (strUserChoice != "Y" || strUserChoice != "N"); } while (strUserChoice == "Y"); // So for the above code, the data structure of List is not most efficient // as we are utilizing that data structure primarily for the use of retrieval. // Therefore since we would already know the 'key' using the dictionary would allow us // to retrieve the value we want in O(1) time. (lookups) // As compared to O(n) time for the List. Dictionary <string, Country> countriesTable = new Dictionary <string, Country>(); countriesTable.Add(country1.Code, country1); countriesTable.Add(country2.Code, country2); countriesTable.Add(country3.Code, country3); do { Console.WriteLine("Please enter country code"); string countryCodeInput = Console.ReadLine().ToUpper(); // Need to check if a key is within a dictionary otherwise // .NET will throw an exception. if (countriesTable.ContainsKey(countryCodeInput)) { var resultCountry = countriesTable[countryCodeInput]; Console.WriteLine("Name = {0}, Capital = {1}", resultCountry.Name, resultCountry.Capital); } else { Console.WriteLine("Country code not valid"); } do { Console.WriteLine("Do you want to continue? (Y or N)"); strUserChoice = Console.ReadLine().ToUpper(); } while (strUserChoice != "Y" || strUserChoice != "N"); } while (strUserChoice == "Y"); /* --------------------- * 82) Generic queue collection class * ---------------------- */ // Queue is a generic FIFO (first in, first out) collection class // that is found in the System.Collections.Generic namespace. // The first one to be added (enqueued) to the queue, // will also be the first item to be removed (dequeued). // To add items to the end, use Enqueue() // To remove at item that is at the beginning use Dequeue() // A foreach loop iterates through the queue, but will not remove it // from the queue. // To check if an item exists, use Contains() // Peek() can be used to return the item at the beginning of the queue, // without removing it. NewCustomer customer3 = new NewCustomer() { ID = 3, Name = "Dan", Salary = 72000 }; Queue <NewCustomer> queueCustomers = new Queue <NewCustomer>(); queueCustomers.Enqueue(new NewCustomer() { ID = 1, Name = "Steven", Salary = 52000 }); queueCustomers.Enqueue(new NewCustomer() { ID = 2, Name = "Joe", Salary = 90000 }); queueCustomers.Enqueue(customer3); queueCustomers.Enqueue(new NewCustomer() { ID = 4, Name = "Tony", Salary = 65000 }); NewCustomer firstCustomer = queueCustomers.Dequeue(); Console.WriteLine($"The first customer is {firstCustomer.Name}"); Console.WriteLine($"Total items in the Queue = {queueCustomers.Count}"); foreach (NewCustomer c in queueCustomers) { Console.WriteLine($"ID = {c.ID}, Name = {c.Name}"); } // To get the item at the beginning of the queue without removing it. firstCustomer = queueCustomers.Peek(); Console.WriteLine($"ID = {firstCustomer.ID}, Name = {firstCustomer.Name}"); if (queueCustomers.Contains(customer3)) { Console.WriteLine("Customer 3 is in the queue."); } else { Console.WriteLine("The customer does not exist in the queue."); } /* --------------------- * 83) Generic stack collection class * ---------------------- */ // Stack is a generic LIFO (last in, first out) collection class that is present // in System.Colllections.Generic namespace. // Think of it as a stack of plates. // The last item is added (pushed) to the stack, // and will be the first item to be removed (popped) from the stack. // To insert an item at the top of the stack, use Push(). // To remove and return the item at the top of the stack, use Pop() // A foreach loop iterates through the items in the stack, but it will not remove // them from the stack. // The last element added to the stack is the first one to be returned. // To check if an item exists in the stack use Contains() // Peek() returns the item at the top of the stack without removing it. Stack <NewCustomer> stackCustomers = new Stack <NewCustomer>(); stackCustomers.Push(new NewCustomer() { ID = 1, Name = "Steven", Salary = 52000 }); stackCustomers.Push(new NewCustomer() { ID = 2, Name = "Joe", Salary = 90000 }); stackCustomers.Push(customer3); stackCustomers.Push(new NewCustomer() { ID = 4, Name = "Tony", Salary = 65000 }); NewCustomer customerOnStack = stackCustomers.Pop(); Console.WriteLine($"First on stack is {customerOnStack.Name}"); Console.WriteLine($"The amount of customers on the stack is {stackCustomers.Count}"); foreach (NewCustomer current in stackCustomers) { Console.WriteLine($"The current customer on the stack is {current.Name}."); } Console.WriteLine($"Retrieve item on top without removing it: {stackCustomers.Peek()}"); if (stackCustomers.Contains(customer3)) { Console.WriteLine("Yes customer 3 is in the stack."); } else { Console.WriteLine("No customer 3 is not in the stack."); } /* --------------------- * 84) Real time example of queue collection class in C# * ---------------------- */ /* --------------------- * 85) Real time example of stack collection class in C# * ---------------------- */ /* --------------------- * 86) Multithreading in C# * ---------------------- */ // What is a process? // A process is what the operating system uses to facilitate the execution of a program // by providing the resources required. Each process has a unique process id // associated with it. You can view the process within which a program is being // executing using windows task manager. // What is a thread? // A thread is a light weight process. A process has at least one thread which is // commonly called as main thread which actually executes the application code. // A single process can have multiple threads. // NOTE: All the threading related classes are present in System.Threading namespace. // So pass in a function name //Thread workerThread = new Thread(DoTimeConsumingWork); // So here we create a new thread such that the UI of the application remains responsive. // workerThread will start execution will begin with // workerThread.Start(); // So the job is offloaded to the thread. // So the benefit of multithreading is that it can make your application more responsive, // as you can offload the work of time consuming functions. /* --------------------- * 87) Advantages and disadvantages of multithreading * ---------------------- */ // Advantages of multithreading: // 1. To maintain a responsive user interface. // 2. To make efficient use of processor time while waiting for I/O operations to complete. // 3. To split large, CPU-bound tasks to be processed simultaneously on a machine that has // multiple processors/cores. // Multithreaded applications can improve the performance by taking advantages of multiple processors/cores // ability to have multiple threads. // Disadvantages of multithreading: // 1. On a single processor/core machine threading can affect performance negatively as there is overhead involved // with context-switching. // As the processor must allocate its time equally amongst its threads. Also time involved to switch between threads. // 2. Have to write more lines of code to accomplish the same task. // 3. Multitheading applications are difficult to write, understand, debug, and maintain. // NOTE: Only use multithreading when the advantages of doing so outweigh the disadvantages. /* --------------------- * 88) ThreadStart delegate * ---------------------- */ // To create a THREAD, create an instance of Thread class and to it's constructor, // pass the name of the function that we want the thread to execute. Thread T1 = new Thread(Number.PrintNumbers); T1.Start(); // Now rewrite it using ThreadStart delegate // There are 4 overloaded version of the Thread constructor, // One option is a ThreadStart delegate // A delegate is a type-safe function pointer. // The signature of the function which the delegate point to should match // the signature of the delegate otherwise you will get a compile error. Thread T2 = new Thread(new ThreadStart(Number.PrintNumbers)); // Why does a delegate need to be passed as a parameter to the Thread class constructor? // The purpose of creating a Thread is to execute a function. A delegate is a type safe function pointer, // meaning that it points to a function that the thread has to execute. // In short, all threads require an entry point to start execution. Any thread you create will // need an explicitly defined entry point i.e. a pointer to the function where they should // begin execution. // So threads always require a delegate. // In the code showed above in the example where we just do // new Thread(Number.PrintNumbers) // It's working in spite of not creating the ThreadStart delegate explicitly because the // framework is doing it automatically for us. // You could also rewrite the same line using delegate() keyword Thread t3 = new Thread(delegate() { Number.PrintNumbers(); }); // The same line written using lambda expression Thread t4 = new Thread(() => Number.PrintNumbers()); // Thread functions do not need to be static functions always. // It can also be a non-static function. In the case of a non-static // function we have to create an instance of the class. Number number = new Number(); Thread t5 = new Thread(number.DisplayNumbers); /* --------------------- * 89) ParameterizedThreadStart delegate * ---------------------- */ // Use ParameterizedThreadStart delegate to pass data to the thread function. Console.WriteLine("Plesae enter the target number"); object target = Console.ReadLine(); ParameterizedThreadStart parameterizedThreadStart = new ParameterizedThreadStart(Number.OutputNumbers); Thread t6 = new Thread(parameterizedThreadStart); t6.Start(target); // Could also be written as... Console.WriteLine("Please enter the target number"); object setTarget = Console.ReadLine(); Thread t7 = new Thread(Number.OutputNumbers); t7.Start(setTarget); // Here we are not explicitly creating an instance of ParameterizedThreadStart // delegate. Then how is it working? // It's working because the compiler implicitly converts // new Thread(Number.PrintNumbers); // to // new Thread(new ParameterizedThreadStart(Number.PrintNumbers)); // When to use ParameterizedThreadStart over ThreadStart delegate? // Use parameterizedThreadStart delegate if you have some data to pass to the // Thread function. Otherwise just use ThreadStart delegate. // NOTE: Using ParameterizedThreadStart delegate and Thread.Start(Object) // method to pass data to the thread function is not type safe as they operate // on object datatype and any type of data can be passed. // If you try to change the data type of the target parameter of OutputNumbers() function // from object to int, a compiler error will be raised as the signature of OutputNumbers() // function does not match with the signature of // ParameterizedThreadStart delegate. /* --------------------- * 90) Passing data to the Thread function in a type safe manner * ---------------------- */ // To pass data to the Thread function in a type safe manner, // encapsulate the thread function and the data it needs in a helper class // and use the ThreadStart delegate to execute the thread functino. Console.WriteLine("Please enter the target number"); int target8 = Convert.ToInt32(Console.ReadLine()); MyNumber myNumber = new MyNumber(target8); Thread t8 = new Thread(new ThreadStart(myNumber.PrintNumbers)); t8.Start(); }