Performance
.NET Processes
Any process have three default streams:
-
Standard input (stdin): This is the only input stream for all terminal or console applications. When you call Console.ReadLine or Console.Read the result is taken from this stream.
-
Standard Output (stdout): When you call output-related commands in the console singleton class, all data goes here. For example the WriteLine or the Write methods.
-
Standard error (stderr): This is a dedicated stream to print error-related content to the console. This is dedicated because some applications and scripting solutions want to hide these messages.
using System;
namespace MyBusiness
{
class Program
{
static void Main(string[] args)
{
string ?name = "";
// Check if parameters are passed
// If no, ask for the name
if (args.Length == 0)
{
Console.WriteLine("Hello, please tell me your name:");
// standard input
name = Console.ReadLine();
}
else
{
name = args[0];
}
// standard output
Console.WriteLine("stdout: Hello {0} ", name);
// standard error
Console.Error.WriteLine("stderr: Hello {0} ", name);
}
}
}
Type the following commands at the terminal,
$ dotnet run
$ dotnet run Yun
// Redirect standard output to a file called stdout.txt
$ dotnet run > stdout.txt
// Redirect standard error to a file called stderr.txt
$ dotnet run 2> stderr.txt
try-catch-finally statement Doc
The try-catch statement consists of a try block followed by one or more catch clauses, which specify handlers for different exceptions.
When an exception is thrown, the common language runtime (CLR) looks for the catch statement that handles this exception.
using System;
using System.IO;
using static System.IO.Path;
using static System.Environment;
namespace MyBusiness
{
class Program
{
static void Main(string[] args)
{
string file = Combine(CurrentDirectory, "mydata.txt");
Console.WriteLine($"my file = {file}");
StreamReader? textReader = null;
// try-catch-finally statement
try
{
textReader = File.OpenText(file);
// output all the contents of the file
Console.WriteLine(textReader.ReadToEnd());
}
catch (Exception ex)
{
Console.WriteLine($"{ex.GetType()} says {ex.Message}");
}
finally
{
// if textReader is not null, we close it
if (textReader != null)
{
textReader.Close();
}
}
Console.WriteLine("End of the program!");
}
}
}
When you have mydata.txt under the folder
my file = /Users/yun/Dropbox/FH/BCC/C-Sharp/Codes/CRC_CSD-10/MyBusiness/mydata.txt
Hello yun
End of the program!
When you don’t have mydata.txt under the folder
my file = /Users/yun/Dropbox/FH/BCC/C-Sharp/Codes/CRC_CSD-10/MyBusiness/mydata.txt
System.IO.FileNotFoundException says Could not find file '/Users/yun/Dropbox/FH/BCC/C-Sharp/Codes/CRC_CSD-10/MyBusiness/mydata.txt'.
End of the program!
Define Exception for Your Own Class
In MyBusiness/Program.cs,
using System;
using Animals;
// namespace
namespace MyBusiness
{
// main program
internal class Program
{
static void Main(string[] args)
{
// Create a cat
Cat coffee = new Cat("Coffee", new DateTime(2022, 3, 20));
try
{
coffee.getPregnant(new DateTime(2023, 3, 31));
coffee.getPregnant(new DateTime(2022, 4, 25));
}
catch (CatException ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
In PetLibrary/Animals.cs,
/*
The animal namespace
*/
namespace Animals
{
public class Cat
{
/*
Class field, including different variables
they can be organized by similar characteristics
*/
// The name of the cat
public string Name;
// The birthday of the cat
public DateTime DateOfBirth;
/*
Class methods, where functions should be implemented
*/
// Constructors
// Default constructor. It will be called by default
public Cat()
{
Name = "Unknown";
DateOfBirth = DateTime.Today;
}
// Parameterized Constructor
public Cat(string name, DateTime dateOfBirth)
{
this.Name = name;
this.DateOfBirth = dateOfBirth;
}
// Finalizer
~Cat()
{
}
// method
public void getPregnant(DateTime day)
{
if ((day.Year - DateOfBirth.Year) < 1)
{
// It is a handler
throw new CatException("The cat is too young for producing a kitty!");
}
else
{
Console.WriteLine($"Ok in {day.Year}!");
}
}
}
public class CatException : Exception
{
// Unlike ordinary methods, constructors are not inherited,
// so we must explicitly declare and explicitly call the base constructor implementations in System.Exception
public CatException() : base() { }
public CatException(string message) : base(message) { }
public CatException(
string message, Exception innerException)
: base(message, innerException) {
}
}
}
$ Ok in 2023!
$ The cat is too young for producing a kitty!
Nested Exception
// Nested exception
try
{
try
{
var num = int.Parse("abc"); // Throws FormatException
}
catch (FormatException fe)
{
try
{
coffee.getPregnant(new DateTime(2022, 4, 25));
}
catch (CatException ex)
{
throw new CatException(ex.Message, fe);
}
}
}
catch (CatException oex)
{
string inMes = "", outMes = "";
if (oex.InnerException != null)
{
// Inner exception (FormatException) message
inMes = oex.InnerException.Message;
}
outMes = oex.Message;
Console.WriteLine($"Inner Exception:\n\t{inMes}");
Console.WriteLine($"Outter Exception:\n\t{outMes}");
}
Inner Exception:
Input string was not in a correct format.
Outter Exception:
The cat is too young for producing a kitty!
Patterns
Patterns are distilled commonality that you find in programs.
-
Design Patterns: solves reoccurring problems in software construction.
-
Architectural Patterns: fundamental structural organization for software systems. Architectural patterns are seen as commonality at higher level than design patterns.
Event-Driven Architecture Pattern
Event-driven architecture (EDA) The event-driven architecture pattern is a popular distributed asynchronous architecture pattern used to produce highly scalable applications.
Raising and handling events
Built-in EventHandler Delegate
In Program.cs
using System;
using Animals;
// namespace
namespace MyBusiness
{
// main program
internal class Program
{
static void Main(string[] args)
{
// Create a cat and initialize it
Cat coffee = new Cat("Coffee", new DateTime(2019, 6, 20));
// coffee.Shout is an event handler
coffee.Shout += Cat_Shout; // register with an event
coffee.Poke();
coffee.Poke();
coffee.Poke();
coffee.Poke();
}
// This is a method outside of the Main() method
private static void Cat_Shout(object sender, EventArgs e)
{
Cat cat = (Cat)sender;
Console.WriteLine($"{cat.Name} is at angry level: {cat.AngerLevel}.");
}
}
}
In Animals.cs
using System;
using System.Collections.Generic;
/*
The animal namespace
*/
namespace Animals
{
// Reuse IComparable<Cat>
public class Cat
{
/*
Class field, including different variables
they can be organized by similar characteristics
*/
// The name of the cat
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public int AngerLevel { get; set; }
/*
Class methods, where functions should be implemented
*/
// Constructors
// Default constructor. It will be called by default
public Cat()
{
Name = "";
}
// Parameterized Constructor
public Cat(string name, DateTime dateOfBirth)
{
this.Name = name;
}
// event delegate field
public event EventHandler? Shout;
// method
public void Poke()
{
AngerLevel++;
if (AngerLevel >= 3)
{
// if something is listening...
if (Shout != null)
{
// ...then call the delegate
Shout.Invoke(this, EventArgs.Empty);
}
}
}
}
}
Coffee is at angry level: 3.
Coffee is at angry level: 4.
Understanding processes, threads, and tasks
-
Process: A process, with one example being each of the console applications we have created has resources like memory and threads allocated to it.
-
Thread: A thread executes your code, statement by statement. By default, each process only has one thread, and this can cause problems when we need to do more than one task at the same time.
Running multiple actions synchronously
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
// namespace
namespace MyBusiness
{
// main program
internal class Program
{
static void Main(string[] args)
{
var timer = Stopwatch.StartNew();
Console.WriteLine("Running methods synchronously on one thread.");
MethodA();
MethodB();
MethodC();
Console.WriteLine($"{timer.ElapsedMilliseconds:#,##0}ms elapsed.");
}
static void MethodA()
{
Console.WriteLine("Starting Method A...");
Thread.Sleep(3000); // simulate three seconds of work
Console.WriteLine("Finished Method A.");
}
static void MethodB()
{
Console.WriteLine("Starting Method B...");
Thread.Sleep(2000); // simulate two seconds of work
Console.WriteLine("Finished Method B.");
}
static void MethodC()
{
Console.WriteLine("Starting Method C...");
Thread.Sleep(1000); // simulate one second of work
Console.WriteLine("Finished Method C.");
}
}
}
$ Running methods synchronously on one thread.
$ Starting Method A...
$ Finished Method A.
$ Starting Method B...
$ Finished Method B.
$ Starting Method C...
$ Finished Method C.
$ 6,033ms elapsed.
Running tasks asynchronously
static void Main(string[] args)
{
var timer = Stopwatch.StartNew();
// WriteLine("Running methods synchronously on one thread.");
// MethodA();
// MethodB();
// MethodC();
Console.WriteLine("Running methods asynchronously on multiple threads.");
Task taskA = new Task(MethodA);
taskA.Start();
Task taskB = Task.Factory.StartNew(MethodB);
Task taskC = Task.Run(new Action(MethodC));
Console.WriteLine($"{timer.ElapsedMilliseconds:#,##0}ms elapsed.");
}
Running methods asynchronously on multiple threads.
$ Starting Method A...
$ Starting Method C...
$ Starting Method B...
$ 22ms elapsed.
[IMPORTANT] It is even possible that the console app will end before one or more of the tasks have a chance to start and write to the console!
Waiting for tasks
Task[] tasks = { taskA, taskB, taskC };
Task.WaitAll(tasks);
$ Running methods asynchronously on multiple threads.
$ Starting Method C...
$ Starting Method A...
$ Starting Method B...
$ Finished Method C.
$ Finished Method B.
$ Finished Method A.
3,024ms elapsed.
Accessing a resource from multiple threads
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
// namespace
namespace MyBusiness
{
// main program
internal class Program
{
static Random r = new Random();
static string Message = ""; // a shared resource
static void Main(string[] args)
{
Console.WriteLine("Please wait for the tasks to complete.");
Stopwatch watch = Stopwatch.StartNew();
Task a = Task.Factory.StartNew(MethodA);
Task b = Task.Factory.StartNew(MethodB);
// Wait until all tasks are finished
Task.WaitAll(new Task[] { a, b });
Console.WriteLine();
Console.WriteLine($"Results: {Message}.");
Console.WriteLine($"{watch.ElapsedMilliseconds:#,##0} elapsed milliseconds.");
}
static void MethodA()
{
// add 5 times char A in the message
for (int i = 0; i < 5; i++)
{
Thread.Sleep(r.Next(2000));
Message += "A";
Console.Write(".");
}
}
static void MethodB()
{
// add 5 times char B in the message
for (int i = 0; i < 5; i++)
{
Thread.Sleep(r.Next(2000));
Message += "B";
Console.Write(".");
}
}
}
}
$ Please wait for the tasks to complete.
$ ..........
$ Results: BABABABAAB.
$ 8,677 elapsed milliseconds.
[IMPORTANT] This shows that both threads were modifying the message concurrently. In an actual application, this could be a problem.
Applying a mutually exclusive lock to a resource
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
// namespace
namespace MyBusiness
{
// main program
internal class Program
{
static Random r = new Random();
static string Message = ""; // a shared resource
static void Main(string[] args)
{
Console.WriteLine("Please wait for the tasks to complete.");
Stopwatch watch = Stopwatch.StartNew();
Task a = Task.Factory.StartNew(MethodA);
Task b = Task.Factory.StartNew(MethodB);
// Wait until all tasks are finished
Task.WaitAll(new Task[] { a, b });
Console.WriteLine();
Console.WriteLine($"Results: {Message}.");
Console.WriteLine($"{watch.ElapsedMilliseconds:#,##0} elapsed milliseconds.");
}
static object conch = new object();
static void MethodA()
{
// add 5 times char A in the message
lock (conch)
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(r.Next(2000));
Message += "A";
Console.Write(".");
}
}
}
static void MethodB()
{
// add 5 times char B in the message
lock (conch)
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(r.Next(2000));
Message += "B";
Console.Write(".");
}
}
}
}
}
$ Please wait for the tasks to complete.
$ ..........
$ Results: AAAAABBBBB.
$ 7,701 elapsed milliseconds.
Understanding the lock statement
lock (conch)
{
// work with shared resource
}
try
{
Monitor.Enter(conch);
// work with shared resource
}
finally
{
Monitor.Exit(conch);
}
Avoiding deadlocks
Replace the MethodA() to the following content by giving a time to avoid potential deadlock.
static void MethodA()
{
// add 5 times char A in the message
try
{
// Add a timer to avoid potential deadlock
if (Monitor.TryEnter(conch, TimeSpan.FromSeconds(15)))
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(r.Next(2000));
Message += "A";
Console.Write(".");
}
}
else
{
Console.WriteLine("Method A failed to enter a monitor lock.");
}
}
finally
{
Monitor.Exit(conch);
}
}
$ Please wait for the tasks to complete.
$ ..........
$ Results: BBBBBAAAAA.
$ 9,263 elapsed milliseconds.