Vous êtes sur la page 1sur 12

1

Threads in Dot Net - C#


C# provides an extensive set of classes in the System.Threading namespace to support multithreading. The basic threading model is similar to Java providing the Monitor mechanism to support synchronization of threads. There are additional primitives equivalent to critical sections, mutexes (for cross process locking of resources), monitor wait and signal. The major features in the Dot Net threading model are summarized below. Thread class is responsible for starting, stopping, suspending, resuming, setting thread priorities to a thread. ThreadStart class specifies the actual thread function to be executed (as a delegate) to the ThreadObject.Start( ) call. The typeical thread creation code looks like: MyClass objname = new MyClass ( ); . Thread threadobj = new Thread(new ThreadStart(objname.funcname( )); .. threadobj.Start( ) ; // will cause the funcname to run as a separate thread

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.

Give the class a name of MyTime as shown below.

Type the following code in MyTime.cs file:


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() {

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)

}
}

public void CheckTime() { // Win32 API for Syncronization ::EnterCriticalSection(&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.

Suspending, Resuming, Changing Thread Priorities:


The thread class allows us to suspend or resume a thread as well as change its priority. There are five different priority levels within a category of a thread (normal, background etc..). These are, normal, above normal, highest, below normal and lowest. Example: Let us create an example similar to the blue dot and red dot example we did in MFC. Create a Windows application in C#. Name the project dots. Add a menu control to the form. Name the menu control mnuMain. Design the menus to look like:

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 {

Thread m_thred, m_thblue; DotDraw m_dotobj; Graphics m_g;


private private private private private private private private private private private System.Windows.Forms.MainMenu System.Windows.Forms.MenuItem System.Windows.Forms.MenuItem System.Windows.Forms.MenuItem System.Windows.Forms.MenuItem System.Windows.Forms.MenuItem System.Windows.Forms.MenuItem System.Windows.Forms.MenuItem System.Windows.Forms.MenuItem System.Windows.Forms.MenuItem System.Windows.Forms.MenuItem mnuMain; mnuThread; mnuThreadSuspend; mnuThreadResume; mnuFile; mnuFileExit; menuItem1; mnuThreadLowBlue; mnuThreadHighBlue; mnuThreadLowRed; mnuThreadHighRed;

/// <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();
}

private void mnuFileExit_Click(object sender, System.EventArgs e) {

m_dotobj.terminate = true; m_thred.Join(); m_thblue.Join(); // wait for threads to finish Application.Exit();


} private void mnuThreadSuspendRed_Click(object sender, System.EventArgs e) {

12 m_thred.Suspend();
} private void mnuThreadResumeRed_Click(object sender, System.EventArgs e) { }

m_thred.Resume();

private void mnuThreadLowBlue_Click(object sender, System.EventArgs e) {

m_thblue.Priority = ThreadPriority.BelowNormal;
} private void mnuThreadNormalBlue_Click(object sender, System.EventArgs e) { }

m_thblue.Priority = ThreadPriority.Normal;

private void mnuThreadLowRed_Click(object sender, System.EventArgs e) {

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; }
} } }

Vous aimerez peut-être aussi