Threads in Dot Net - C#: Example
Threads in Dot Net - C#: Example
Example: Let us take the MFC example on using the Time Class object and calling the
IncrementTime and GetTime functions on it. Create a new C# project in Visual Studio 7 (Dot Net). Name the project ThreadTime.
Right Click on the project name in the Solution explorer window, then choose, Add -> Add Class, as shown below.
3
hours = 3; // initial time when object is created minutes = 59; seconds = 59;
} public void IncrementTime() { // Win32 API ::EnterCriticalSection(&gCS); seconds ++; if (seconds == 60) { seconds = 0; minutes ++; if (minutes == 60) { minutes = 0; Thread.Sleep(1000); // try without this line // it will produce wrong results with Sleep hours ++; if (hours == 13) hours = 1; } } // Win32 API for synchronization ::LeaveCriticalSection(&gCS); } public void CheckTime() { // Win32 API for Syncronization ::EnterCriticalSection(&gCS); System.Console.WriteLine ("Current Time = hours: {0} minutes: {1} seconds: {2}", hours, minutes, seconds); // ::LeaveCriticalSection(&gCS); } } }
Add another file to the project called TestTime.cs. Delete the existing Class1.cs file by selecting it in the solution explorer and hitting the delete key. The code in the TestTime.cs will look like:
using System; using System.Threading; namespace ThreadTime { /// <summary> /// Summary description for TestTime. /// </summary> public class TestTime { public TestTime() { // // TODO: Add constructor logic here
4
// } static void Main() { MyTime t1 = new MyTime(); Thread myth = new Thread(new ThreadStart(t1.IncrementTime)); // set the delegate myth.Start(); // run increment time as a separate thread Thread.Sleep(100); t1.CheckTime(); // should report 4:0:0 if synchronized properly } } }
Choose Build from the Build menu. If there are no errors, execute the program by choosing Debug-> Start without Debugging menu item. The output of the program will look like:
Note that the expected output is hours:4 minutes:0 seconds:0. We need to synchronize the IncrementTime thread and the main thread. We can achieve this by either using the lock primitive. Modify the MyTime.cs to as shown below:
using System; using System.Threading; // added by Ausif namespace ThreadTime { /// <summary> /// Summary description for MyTime. /// </summary> public class MyTime { int hours; int minutes; int seconds; public MyTime() { hours = 3; // initial time when object is created minutes = 59; seconds = 59; } public void IncrementTime() { // Win32 API ::EnterCriticalSection(&gCS);
5 lock(this) {
seconds ++; if (seconds == 60) { seconds = 0; minutes ++; if (minutes == 60) { minutes = 0; Thread.Sleep(1000); // try without this line // it will produce wrong results with Sleep hours ++; if (hours == 13) hours = 1; } } // end of lock(this) Monitor.Exit() // ::LeaveCriticalSection(&gCS)
}
}
lock(this) {
System.Console.WriteLine ("Current Time = hours: {0} minutes: {1} seconds: {2}", hours, minutes, seconds); } // ::LeaveCriticalSection(&gCS);
}
} }
Now if you rebuild and execute the program, it will show the correct results.
The File menu has Exit underneath it. Name the menus properly, e.g., mnuFile for the File, mnuFileExit for the File->Exit menu etc.. By Right clicking on the dots project in the solution explorer, add a class called DotDraw to the project. Type the following code in it:
using System; using System.Threading; using System.Drawing; namespace dots { /// <summary> /// Summary description for DotDraw. /// </summary> public class DotDraw { Random m_r; public int m_cx, m_cy; // width height of client area public Graphics m_gc; Pen redPen; Pen bluePen; public bool terminate; public DotDraw(Graphics g, int cw, int cy) { m_gc = g;
7
m_cx = cw; m_cy = cy; redPen = new Pen(Color.Red,1); // 1 pixel wide bluePen = new Pen(Color.Blue,1); // 1 pixel wide m_r = new Random(); this.terminate = false;
public void RedDot() { // cx and cy are client area width and height Point p1 = new Point(); Point p2 = new Point(); while (terminate == false) { int rnum = m_r.Next(m_cx); // random number from 0 to cx int cnum = m_r.Next(m_cy); p1.X = rnum; p1.Y = cnum; if ((rnum < m_cx) && (cnum < m_cy)) { p2.X = rnum+1; p2.Y = cnum+1; lock (this) { m_gc.DrawLine(redPen,p1,p2); // 1 pixel line } }
} }
public void BlueDot() { // cx and cy are client area width and height Point p1 = new Point(); Point p2 = new Point(); while (terminate == false) { int rnum = m_r.Next(m_cx); // random number from 0 to cx int cnum = m_r.Next(m_cy); p1.X = rnum; p1.Y = cnum; if ((rnum < m_cx) && (cnum < m_cy)) { p2.X = rnum+1; p2.Y = cnum+1; lock (this) { m_gc.DrawLine(bluePen,p1,p2); // 1 pixel line } } //Thread.Sleep(100); } } } }
The RedDot and BlueDot methods in the above class will be invoked as two separate threads. The Graphics object is needed to do any output to the client area (you can think
8 of it as the equivalent of the device context in MFC). There is a SetPixel method but only available for Bitmaps. This is the reason I am using the DrawLine method in the above class to color a pixel. Also since the Graphics object is going to be shared by multiple threads, we need to protect it by the lock synchronization mechanism. The code for the main form class is shown below. Note that you will be adding the code for the event handlers by first double clicking on a menu item, then adding the code to the handler function.
using using using using using using System; System.Drawing; System.Collections; System.ComponentModel; System.Windows.Forms; System.Data;
using System.Threading;
namespace dots { /// <summary> /// Summary description for Form1. /// </summary> public class Form1 : System.Windows.Forms.Form {
/// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.Container components = null; public Form1() { // // Required for Windows Form Designer support // InitializeComponent(); // // TODO: Add any constructor code after InitializeComponent call //
9
} /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.mnuMain = new System.Windows.Forms.MainMenu(); this.mnuFile = new System.Windows.Forms.MenuItem(); this.mnuFileExit = new System.Windows.Forms.MenuItem(); this.mnuThread = new System.Windows.Forms.MenuItem(); this.mnuThreadSuspend = new System.Windows.Forms.MenuItem(); this.mnuThreadResume = new System.Windows.Forms.MenuItem(); this.menuItem1 = new System.Windows.Forms.MenuItem(); this.mnuThreadLowBlue = new System.Windows.Forms.MenuItem(); this.mnuThreadHighBlue = new System.Windows.Forms.MenuItem(); this.mnuThreadLowRed = new System.Windows.Forms.MenuItem(); this.mnuThreadHighRed = new System.Windows.Forms.MenuItem(); // // mnuMain // this.mnuMain.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.mnuFile, this.mnuThread}); // // mnuFile // this.mnuFile.Index = 0; this.mnuFile.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.mnuFileExit}); this.mnuFile.Text = "File"; // // mnuFileExit // this.mnuFileExit.Index = 0; this.mnuFileExit.Text = "Exit"; this.mnuFileExit.Click += new System.EventHandler(this.mnuFileExit_Click);
10
// // mnuThread // this.mnuThread.Index = 1; this.mnuThread.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.mnuThreadSuspend, this.mnuThreadResume, this.menuItem1, this.mnuThreadLowBlue, this.mnuThreadHighBlue, this.mnuThreadLowRed, this.mnuThreadHighRed}); this.mnuThread.Text = "Thread"; // // mnuThreadSuspend // this.mnuThreadSuspend.Index = 0; this.mnuThreadSuspend.Text = "Suspend Thread Red"; this.mnuThreadSuspend.Click += new System.EventHandler(this.mnuThreadSuspendRed_Click); // // mnuThreadResume // this.mnuThreadResume.Index = 1; this.mnuThreadResume.Text = "Resume Thread Red"; this.mnuThreadResume.Click += new System.EventHandler(this.mnuThreadResumeRed_Click); // // menuItem1 // this.menuItem1.Index = 2; this.menuItem1.Text = "-"; // // mnuThreadLowBlue // this.mnuThreadLowBlue.Index = 3; this.mnuThreadLowBlue.Text = "LowPriorityBlue"; this.mnuThreadLowBlue.Click += new System.EventHandler(this.mnuThreadLowBlue_Click); // // mnuThreadHighBlue // this.mnuThreadHighBlue.Index = 4; this.mnuThreadHighBlue.Text = "PriorityBlue Normal"; this.mnuThreadHighBlue.Click += new System.EventHandler(this.mnuThreadNormalBlue_Click); // // mnuThreadLowRed // this.mnuThreadLowRed.Index = 5; this.mnuThreadLowRed.Text = "LowPriorityRed"; this.mnuThreadLowRed.Click += new System.EventHandler(this.mnuThreadLowRed_Click); // // mnuThreadHighRed //
11
this.mnuThreadHighRed.Index = 6; this.mnuThreadHighRed.Text = "PriorityRed Normal"; this.mnuThreadHighRed.Click += new System.EventHandler(this.mnuThreadNormalRed_Click); // // Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(474, 318); this.Menu = this.mnuMain; this.Name = "Form1"; this.Text = "Form1"; this.Resize += new System.EventHandler(this.Form1_Resize); this.Load += new System.EventHandler(this.Form1_Load); } #endregion /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Application.Run(new Form1()); } private void Form1_Load(object sender, System.EventArgs e) {
m_g = this.CreateGraphics(); int cx = this.ClientRectangle.Width; int cy = this.ClientRectangle.Height; m_dotobj = new DotDraw(m_g,cx,cy); m_thred = new Thread(new ThreadStart(m_dotobj.RedDot)); m_thred.Start(); m_thblue = new Thread(new ThreadStart(m_dotobj.BlueDot)); m_thblue.Start();
}
12 m_thred.Suspend();
} private void mnuThreadResumeRed_Click(object sender, System.EventArgs e) { }
m_thred.Resume();
m_thblue.Priority = ThreadPriority.BelowNormal;
} private void mnuThreadNormalBlue_Click(object sender, System.EventArgs e) { }
m_thblue.Priority = ThreadPriority.Normal;
m_thred.Priority = ThreadPriority.BelowNormal;
} private void mnuThreadNormalRed_Click(object sender, System.EventArgs e) {
m_thred.Priority = ThreadPriority.Normal;
} private void Form1_Resize(object sender, System.EventArgs e) {
if (m_dotobj != null) { int cx = this.ClientRectangle.Width; int cy = this.ClientRectangle.Height; m_g = CreateGraphics(); m_dotobj.m_gc = m_g; m_dotobj.m_cx = cx; m_dotobj.m_cy = cy; }
} } }