this is the pic has taken while im doing my work
Sunday, May 30, 2010
programs on accessing databases
Accessing Databases
12.1 Introduction
The .NET Framework uses ADO.NET to access databases. ADO.NET is a set of
managed classes within the .NET Framework. It operates on a disconnected data
access model. When an application requests data a new database connection is
created and destroyed when the request is completed. A disconnected data access
model is frugal with resources, which is desirable.
One important advantage of ADO.NET is that information is stored and transferred
in XML.
ADO.NET allows access to many different databases. There are two basic types of
connections. SQLClient is used for Microsoft’s SQL Server and OLEDB is used for
all other database formats.
In order to use SQL with SQL Server or SQL Server Express the following namespace
must be specified.
using System.Data.SqlClient;
In order to use OLEDB the following namespace must be specified.
using System.Data.OleDb;
When designing a database, the correct datatypes must be chosen. Note the following.
• Choosing an incorect datatype can degrade performance, because of data conversion.
• Choosing too small a datatype cannot meet the system need and at some point
your system may become useless.
• Choosing too large a datatype can waste space and increase capital expenses.
156
12.1. INTRODUCTION 157
Accessing a database requires that a .NET Data Provider is set up. A Data Provider
consists of
• Connection
• Command
• DataReader
• DataAdapter
A Dataset can be understood as a virtual database table that resides in RAM. A
DataSet can contain a set of tables with all the metadata necessary to represent
the structure of the original database. It can be manipulated and updated independantly
of the database. A DataSet is not always required.
The Connection creates the actual connection to the database. The Connection
object contains all the information needed to open the connection, for example the
userid and password. A Connection String is required to connect to a database.
The easiest way to find the correct connection string for a DBMS is to search the
Web. A good site to start with is http://www.connectionstrings.com.
In order to open and close a database connection the Connection methods Open()
and Close() are used respectively.
The Command executes an instruction against the database. For example, this
could be a SQL query or a stored procedure. There are three options for executing
a Command.
1. ExecuteReader: Used for accessing data. Can return multiple rows. Readonly,
forward-only.
2. ExecuteScalar: Used to retrieve a value from a single field.
3. ExecuteNonQuery: Used for data manipulation, such as Delete, Update and
Insert.
A DataReader is used for fast and efficient forward-reading, read-only database
access. It returns individual rows directly to the application. The data is not cached.
In the case that the data is read-only and rarely updated, then a DataAdapter can
be used. A DataAdapter handles retrieving and updating data. It is a middle-layer
between the database (Data Provider) and the disconnected DataSet. It decouples
the DataSet from the database and allows a single DataSet to represent more than
one database. The data adapter fills a DataSet object when reading the data and
writes in a single batch when writing changes back to the data base.
158 CHAPTER 12. ACCESSING DATABASES
An important control is the SqlDataSource. It allows connections to most relational
databases. The default provider is for Microsoft SQL Server, but providers
for other databases are provided. For example, for Oracle.
An important concept is that of Data-Bound controls. They provide a link
between the data source controls and the user. Any data source control can be
selected and linked to any data-bound controls.
12.2 Examples
The following program shows how to connect to a Microsoft SQL Server 2005 or
Sql Express database and retrieve some basic information about the server. Care
must be taken is creating an appropriate connection string (CON STRING in these
examples). Knowledge of SQL is required.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Text;
namespace csDB
{
class OpenSQLDB
{
public const string CON_STRING = "Data Source=mySqlDB;Initial
Catalog=dbTest;User ID=JoeUser;Password=User1234";
static void Main(string[] args)
{
Console.WriteLine("Connecting to db\n");
SqlConnection connection = new SqlConnection(CON_STRING);
connection.Open();
Console.WriteLine("Server Version: {0}",
connection.ServerVersion);
Console.WriteLine("Database: {0}",
connection.Database);
Console.WriteLine("Data Source: {0}",
connection.DataSource);
Console.WriteLine("Stats Enabled: {0}",
connection.StatisticsEnabled);
connection.Close();
} // end Main
} // end class
} // end namespace
12.2. EXAMPLES 159
Embedding the user ID and password, as in the above code, is not a good idea; a
change in the database means the code will have to be modified and recompiled. In
addition, it is not secure. ASP.NET solves this problem by using the web.config file.
The following is an extract from a web.config file.
value="server=mySqlDB.newdb,net;initial
catalog=dbTest;user id=JoeUser;pwd=User1234;"/>
...
<\configuration>
The following line of code fetches the connection string from web.config.
string CON_STRING=ConfigurationSettings.Appsettings["DBConnect"];
The basic method of retrieving data using a Data Reader is as follows. A standard
SQL query is used and submitted to the database via a SqlCommand object. In the
case of data retrieval the SELECT statement is used.
SqlCommand cmDB = new SqlCommand();
cmDB.Connection = connection ;
cmDB.CommandText = "SELECT * FROM tblCustomer";
SqlDataReader myReader = cmDB.ExecuteReader();
while(myReader.Read())
{
// Do something with the data
// In the following code, the myReader[0]
// accesses the FIRST field of data in the
// the selected record. myReader[1] then
// accesses the SECOND field.
// ---------------------------------------
// string strTmp = myReader[0].ToString() +
// " " + myReader[1].ToString();
}
myReader.Close();
A wildcard search can be achieved as follows. The assumption is that a TextBox
called txtSearch is used to input the search parameters. A Trim() is used to remove
whitespace.
160 CHAPTER 12. ACCESSING DATABASES
string strSearch =
"SELECT * FROM tblCustomer where fldCustName LIKE ’" +
TextBox1.Text.Trim() + "%’"; // SQL uses a % for wildcard
myReader = cmDB.ExecuteReader();
The following code shows how to access particular fields using a Data Reader.
// Access SQL EXPRESS db and retrieve data and put field
// values into variables
string CON_STRING =
"Data Source=ejd\\sqlexpress;" +
"Initial Catalog=dbCustomer;Integrated Security=True";
string strSQL =
"SELECT * FROM tblCustomer WHERE" +
" fldCustSurname = ’Hardy’ AND fldCustName = ’Frank’";
int id; string strName, strSurname; float fltCred, fltBal;
SqlConnection connection = new SqlConnection(CON_STRING);
connection.Open();
SqlCommand cmDB = new SqlCommand();
cmDB.Connection = connection;
cmDB.CommandText = strSQL;
SqlDataReader rdr = cmDB.ExecuteReader();
while (rdr.Read())
{
id = (int) rdr["fldCustomerID"];
strName = (string) rdr["fldCustName"];
// etc etc
TextBox1.Text = id + " " + strName;
}
rdr.Close();
connection.Close();
The following code shows how to insert a row into a database, using a SQL INSERT
command.
String CON_STRING
= @"Data Source=mname\sqlexpress;" +
"Initial Catalog=dbCustomer" +
";Integrated Security=True";
SqlConnection connection = new SqlConnection(CON_STRING);
12.2. EXAMPLES 161
connection.Open();
String MyString
= @"INSERT INTO tblCustomer(fldCustName, fldCustSurname)" +
"VALUES(’Angus’, ’MacBeth’)";
SqlCommand MyCmd = new SqlCommand(MyString, connection);
MyCmd.ExecuteScalar();
connection.Close();
It is likely that the C# programmer will have a need to extract some data from a
TextBox, either on a Windows or Web Form, and insert that value into a table. In
the case of wanting to insert the values from two TextBoxes called txtFirstName
and txtSurname, you would have the following statement in C#.
String MyString
= @"INSERT INTO tblCustomer(fldCustName, fldCustSurname)"
+ "VALUES(’"
+ txtFirstName.Text.Trim()
+ "’, ’"
+ txtSurname.Text.Trim()
+ "’)";
The following code shows how to edit a row in a database, using a SQL UPDATE
command.
string CON_STRING =
@"Data Source=mname\sqlexpress;" +
"Initial Catalog=dbCustomer;Integrated Security=True";
SqlConnection connection = new SqlConnection(CON_STRING);
connection.Open();
String MyString =
"UPDATE tblCustomer SET fldCustCred = ’1000’ " +
"WHERE fldCustName = ’Angus’";
SqlCommand MyCmd = new SqlCommand(MyString, connection);
MyCmd.ExecuteNonQuery();
connection.Close();
The following code shows how to delete data in a database, using a SQL DELETE
command. This program uses a TRY... CATCH block for exception handling.
string CON_STRING =
"Data Source=mname\\sqlexpress;" +
"Initial Catalog=dbCustomer;" +
"Integrated Security=True";
162 CHAPTER 12. ACCESSING DATABASES
SqlConnection connection = new SqlConnection(CON_STRING);
try
{
connection.Open();
String MyString = @"DELETE FROM tblCustomer " +
"WHERE fldCustName = ’Greg’";
SqlCommand MyCmd = new SqlCommand(MyString, connection);
MyCmd.ExecuteScalar();
}
catch (SqlException sqlexp)
{
// SqlException catches SQL specific exceptions
// Use it when accessing SQL databases
}
catch (Exception ex)
{
TextBox1.Text = ex.Message;
}
finally
{
// The following code checks if the database is still open
// If it is, then it is closed
if(connection.State == ConnectionState.Open) connection.Close();
}
12.1 Introduction
The .NET Framework uses ADO.NET to access databases. ADO.NET is a set of
managed classes within the .NET Framework. It operates on a disconnected data
access model. When an application requests data a new database connection is
created and destroyed when the request is completed. A disconnected data access
model is frugal with resources, which is desirable.
One important advantage of ADO.NET is that information is stored and transferred
in XML.
ADO.NET allows access to many different databases. There are two basic types of
connections. SQLClient is used for Microsoft’s SQL Server and OLEDB is used for
all other database formats.
In order to use SQL with SQL Server or SQL Server Express the following namespace
must be specified.
using System.Data.SqlClient;
In order to use OLEDB the following namespace must be specified.
using System.Data.OleDb;
When designing a database, the correct datatypes must be chosen. Note the following.
• Choosing an incorect datatype can degrade performance, because of data conversion.
• Choosing too small a datatype cannot meet the system need and at some point
your system may become useless.
• Choosing too large a datatype can waste space and increase capital expenses.
156
12.1. INTRODUCTION 157
Accessing a database requires that a .NET Data Provider is set up. A Data Provider
consists of
• Connection
• Command
• DataReader
• DataAdapter
A Dataset can be understood as a virtual database table that resides in RAM. A
DataSet can contain a set of tables with all the metadata necessary to represent
the structure of the original database. It can be manipulated and updated independantly
of the database. A DataSet is not always required.
The Connection creates the actual connection to the database. The Connection
object contains all the information needed to open the connection, for example the
userid and password. A Connection String is required to connect to a database.
The easiest way to find the correct connection string for a DBMS is to search the
Web. A good site to start with is http://www.connectionstrings.com.
In order to open and close a database connection the Connection methods Open()
and Close() are used respectively.
The Command executes an instruction against the database. For example, this
could be a SQL query or a stored procedure. There are three options for executing
a Command.
1. ExecuteReader: Used for accessing data. Can return multiple rows. Readonly,
forward-only.
2. ExecuteScalar: Used to retrieve a value from a single field.
3. ExecuteNonQuery: Used for data manipulation, such as Delete, Update and
Insert.
A DataReader is used for fast and efficient forward-reading, read-only database
access. It returns individual rows directly to the application. The data is not cached.
In the case that the data is read-only and rarely updated, then a DataAdapter can
be used. A DataAdapter handles retrieving and updating data. It is a middle-layer
between the database (Data Provider) and the disconnected DataSet. It decouples
the DataSet from the database and allows a single DataSet to represent more than
one database. The data adapter fills a DataSet object when reading the data and
writes in a single batch when writing changes back to the data base.
158 CHAPTER 12. ACCESSING DATABASES
An important control is the SqlDataSource. It allows connections to most relational
databases. The default provider is for Microsoft SQL Server, but providers
for other databases are provided. For example, for Oracle.
An important concept is that of Data-Bound controls. They provide a link
between the data source controls and the user. Any data source control can be
selected and linked to any data-bound controls.
12.2 Examples
The following program shows how to connect to a Microsoft SQL Server 2005 or
Sql Express database and retrieve some basic information about the server. Care
must be taken is creating an appropriate connection string (CON STRING in these
examples). Knowledge of SQL is required.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Text;
namespace csDB
{
class OpenSQLDB
{
public const string CON_STRING = "Data Source=mySqlDB;Initial
Catalog=dbTest;User ID=JoeUser;Password=User1234";
static void Main(string[] args)
{
Console.WriteLine("Connecting to db\n");
SqlConnection connection = new SqlConnection(CON_STRING);
connection.Open();
Console.WriteLine("Server Version: {0}",
connection.ServerVersion);
Console.WriteLine("Database: {0}",
connection.Database);
Console.WriteLine("Data Source: {0}",
connection.DataSource);
Console.WriteLine("Stats Enabled: {0}",
connection.StatisticsEnabled);
connection.Close();
} // end Main
} // end class
} // end namespace
12.2. EXAMPLES 159
Embedding the user ID and password, as in the above code, is not a good idea; a
change in the database means the code will have to be modified and recompiled. In
addition, it is not secure. ASP.NET solves this problem by using the web.config file.
The following is an extract from a web.config file.
value="server=mySqlDB.newdb,net;initial
catalog=dbTest;user id=JoeUser;pwd=User1234;"/>
...
<\configuration>
The following line of code fetches the connection string from web.config.
string CON_STRING=ConfigurationSettings.Appsettings["DBConnect"];
The basic method of retrieving data using a Data Reader is as follows. A standard
SQL query is used and submitted to the database via a SqlCommand object. In the
case of data retrieval the SELECT statement is used.
SqlCommand cmDB = new SqlCommand();
cmDB.Connection = connection ;
cmDB.CommandText = "SELECT * FROM tblCustomer";
SqlDataReader myReader = cmDB.ExecuteReader();
while(myReader.Read())
{
// Do something with the data
// In the following code, the myReader[0]
// accesses the FIRST field of data in the
// the selected record. myReader[1] then
// accesses the SECOND field.
// ---------------------------------------
// string strTmp = myReader[0].ToString() +
// " " + myReader[1].ToString();
}
myReader.Close();
A wildcard search can be achieved as follows. The assumption is that a TextBox
called txtSearch is used to input the search parameters. A Trim() is used to remove
whitespace.
160 CHAPTER 12. ACCESSING DATABASES
string strSearch =
"SELECT * FROM tblCustomer where fldCustName LIKE ’" +
TextBox1.Text.Trim() + "%’"; // SQL uses a % for wildcard
myReader = cmDB.ExecuteReader();
The following code shows how to access particular fields using a Data Reader.
// Access SQL EXPRESS db and retrieve data and put field
// values into variables
string CON_STRING =
"Data Source=ejd\\sqlexpress;" +
"Initial Catalog=dbCustomer;Integrated Security=True";
string strSQL =
"SELECT * FROM tblCustomer WHERE" +
" fldCustSurname = ’Hardy’ AND fldCustName = ’Frank’";
int id; string strName, strSurname; float fltCred, fltBal;
SqlConnection connection = new SqlConnection(CON_STRING);
connection.Open();
SqlCommand cmDB = new SqlCommand();
cmDB.Connection = connection;
cmDB.CommandText = strSQL;
SqlDataReader rdr = cmDB.ExecuteReader();
while (rdr.Read())
{
id = (int) rdr["fldCustomerID"];
strName = (string) rdr["fldCustName"];
// etc etc
TextBox1.Text = id + " " + strName;
}
rdr.Close();
connection.Close();
The following code shows how to insert a row into a database, using a SQL INSERT
command.
String CON_STRING
= @"Data Source=mname\sqlexpress;" +
"Initial Catalog=dbCustomer" +
";Integrated Security=True";
SqlConnection connection = new SqlConnection(CON_STRING);
12.2. EXAMPLES 161
connection.Open();
String MyString
= @"INSERT INTO tblCustomer(fldCustName, fldCustSurname)" +
"VALUES(’Angus’, ’MacBeth’)";
SqlCommand MyCmd = new SqlCommand(MyString, connection);
MyCmd.ExecuteScalar();
connection.Close();
It is likely that the C# programmer will have a need to extract some data from a
TextBox, either on a Windows or Web Form, and insert that value into a table. In
the case of wanting to insert the values from two TextBoxes called txtFirstName
and txtSurname, you would have the following statement in C#.
String MyString
= @"INSERT INTO tblCustomer(fldCustName, fldCustSurname)"
+ "VALUES(’"
+ txtFirstName.Text.Trim()
+ "’, ’"
+ txtSurname.Text.Trim()
+ "’)";
The following code shows how to edit a row in a database, using a SQL UPDATE
command.
string CON_STRING =
@"Data Source=mname\sqlexpress;" +
"Initial Catalog=dbCustomer;Integrated Security=True";
SqlConnection connection = new SqlConnection(CON_STRING);
connection.Open();
String MyString =
"UPDATE tblCustomer SET fldCustCred = ’1000’ " +
"WHERE fldCustName = ’Angus’";
SqlCommand MyCmd = new SqlCommand(MyString, connection);
MyCmd.ExecuteNonQuery();
connection.Close();
The following code shows how to delete data in a database, using a SQL DELETE
command. This program uses a TRY... CATCH block for exception handling.
string CON_STRING =
"Data Source=mname\\sqlexpress;" +
"Initial Catalog=dbCustomer;" +
"Integrated Security=True";
162 CHAPTER 12. ACCESSING DATABASES
SqlConnection connection = new SqlConnection(CON_STRING);
try
{
connection.Open();
String MyString = @"DELETE FROM tblCustomer " +
"WHERE fldCustName = ’Greg’";
SqlCommand MyCmd = new SqlCommand(MyString, connection);
MyCmd.ExecuteScalar();
}
catch (SqlException sqlexp)
{
// SqlException catches SQL specific exceptions
// Use it when accessing SQL databases
}
catch (Exception ex)
{
TextBox1.Text = ex.Message;
}
finally
{
// The following code checks if the database is still open
// If it is, then it is closed
if(connection.State == ConnectionState.Open) connection.Close();
}
programs on remoting
Remoting
11.1 Introduction
Object running in one Virtual Machine (VM) should be able to access (call functions
on) objects that are in a different Virtual Machine. Usually this means the two
processes are running on two different computers. To set up remoting we proceed
as follows:
Instantiate target object (on server)
Setup our channels
Register our object (on server)
Get a reference to object (on client)
Call method on server from the client
For example on the Server side:
public class Server {
public static void Main() {
Target t = new Target();
RemotingServices.Marshal(t,"me");
IChannel c = new HttpChannel(8080);
ChannelServices.RegisterChannel(c);
Console.WriteLine("Server ready. ");
Console.ReadLine();
}
}
147
148 CHAPTER 11. REMOTING
On the Client side we have
public class Client {
public static void Main() {
string url = "http://localhost:8080/me";
Type to = typeof(Target);
Target t = (Target) RemotingServices.Connect(to,url);
Console.WriteLine("Connected to server.");
try {
string msg = Console.ReadLine();
t.sendMessage(msg);
} catch(Exception e) {
Console.WriteLine("Error " + e.Message);
}
}
}
A shared class (i.e. it is on the Client and Server side) is
public class Target : System.MarshalByRefObject
{
public void sendMessage(string msg)
{ System.Console.WriteLine(msg); }
}
Reference types passed via remoting must be serializable or marshal-by-reference.
Serializable == pass-by-value
Copy of object is made.
MarshalByRefObject == pass-by-reference
proxy object is passed.
An example for the second case is:
public class Counter : MarshalByRefObject {
int count = 0;
public void incCount() { count++; }
public int getCount() { return count; }
}
An example for the first case is:
11.1. INTRODUCTION 149
[Serializable]
public class Counter {
int count = 0;
public void incCount() { count++; }
public int getCount() { return count; }
}
We have to import:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http; // for Http
using System.Runtime.Remoting.Channels.Tcp; // for Tcp
Shared objects must be available to both the client and the server. Make a separate
assembly for it
csc /t:library Shared.cs
csc /r:Shared.dll Server.cs
csc /r:Shared.dll Client.cs
There are three main items in the process of remoting. The Interface, the Client
and the Server. The Interface is a .dll file (generated from a .cs file) which is on
the Client and Server side. The Server provides an implementation of the Interface
and makes it available to call. The Client calls the Server using the Interface.
150 CHAPTER 11. REMOTING
Example. The client provides a string to the server and the server returns the
length of the string.
We first create an interface with the file MyInterface.cs.
// MyInterface.cs
public interface MyInterface
{
int FunctionOne(string str);
}
MyInterface.cs is on the Server and Client side. We compile to MyInterface.dll
on the Client and Server side with
csc /t:library MyInterface.cs
On the Server side we now create our class which we invoke from a remote machine
(client). RemoteObject.cs is on the Server side and is the implementation of
MyInterface.cs.
// RemoteObject.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
public class MyRemoteClass : MarshalByRefObject, MyInterface
{
public int FunctionOne(string str)
{
return str.Length; // length of string
}
}
Any class derived from MarshalByRefObject allows remote clients to invoke its
methods. Now on the Server side we compile
csc /t:library /r:MyInterface.dll RemoteObject.cs
This creates the file RemoteObject.dll on the Server side.
11.1. INTRODUCTION 151
Next we provide our Server on the Server side.
// ServerTcp.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class MyServer
{
public static void Main()
{
TcpChannel m_TcpChan = new TcpChannel(9999);
ChannelServices.RegisterChannel(m_TcpChan,false);
RemotingConfiguration.RegisterWellKnownServiceType(
Type.GetType("MyRemoteClass,RemoteObject"),
"FirstRemote",WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press ENTER to quit");
System.Console.ReadLine();
} // end Main
} // end class MyServer
First we create a TcpChannel object which we use to transport messages across our
remoting boundary. We select 9999 as the TCP port to listen on. This could cause
problems with firewalls. We use
ChannelServices.RegisterChannel
to register our TcpChannel with the channel services. Then we use
RemoteConfiguration.RegisterWellKnownServiceType
to register our RemoteClass object as a well-known type. Now this object can be
remoted.
We are using the string FirstRemote here which will be used as part of the URL
that the client uses to access the remote object. We use SingleCall mode here
which means a new instance is created for each remote call.
Remark. One could have also used Singleton in which case one object would have
been instantiated and used by all connecting clients.
Next we generate the exe file for ServerTcp via
csc /r:RemoteObject.dll ServerTcp.cs
152 CHAPTER 11. REMOTING
Now we write our Client program ClientTcp.cs on the client side.
// ClientTcp.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class MyClient
{
public static void Main()
{
TcpChannel m_TcpChan = new TcpChannel();
ChannelServices.RegisterChannel(m_TcpChan,false);
MyInterface m_RemoteObject =
(MyInterface) Activator.GetObject(typeof(MyInterface),
"tcp://192.168.0.3:9999/FirstRemote");
Console.WriteLine(m_RemoteObject.FunctionOne("willi hans steeb"));
// for LocalHost
// "tcp://LocalHost:9999/FirstRemote");
} // end Main
} // end class MyClient
Just as in the Server we have a TcpChannel object, though in this case we do not
have to specify a port. We also use
ChannelService.RegisterChannel
to register the channel. We use
Activator.GetObject
to obtain an instance of the MyInterface object. The IP address of the remote
machine (Server) is 192.168.0.3 with the port 9999. The string FirstRemote
which forms part of the URL was that we passed to
RemotingConfiguration.RegisterWellKnownServiceType
on the Server.
To obtain the ClientTcp.exe file we run
csc /r:MyInterface.dll ClientTcp.cs
11.1. INTRODUCTION 153
To run everything on the Server side we enter the exe file at the command line
ServerTcp
Then on the Client side we enter the exe file at the command line
ClientTcp
Then we are provide with the length of the string. In the present case 16.
154 CHAPTER 11. REMOTING
Instead of using Tcp we can also use Http. Then the files ServerTcp.cs and
ClientTcp.cs are replaced by ServerHttp.cs and ClientHttp.cs shown below.
// ServerHttp.cs
// on Server side
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class MyServer
{
public static void Main()
{
HttpChannel chan = new HttpChannel(8080);
ChannelServices.RegisterChannel(chan,false);
RemotingConfiguration.RegisterWellKnownServiceType(
Type.GetType("MyRemoteClass,RemoteObject"),
"FirstRemote",WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press ENTER to quit");
System.Console.ReadLine();
} // end Main
} // end class MyServer
11.1. INTRODUCTION 155
// Client.cs
// on client side
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class MyClient
{
public static void Main()
{
HttpChannel chan = new HttpChannel();
ChannelServices.RegisterChannel(chan,false);
MyInterface m_RemoteObject =
(MyInterface) Activator.GetObject(typeof(MyInterface),
"http://152.106.41.42:8080/FirstRemote");
Console.WriteLine(m_RemoteObject.FunctionOne("willi hans"));
// for LocalHost
// "tcp://LocalHost:9999/FirstRemote");
} // end Main
} // end class MyClient
11.1 Introduction
Object running in one Virtual Machine (VM) should be able to access (call functions
on) objects that are in a different Virtual Machine. Usually this means the two
processes are running on two different computers. To set up remoting we proceed
as follows:
Instantiate target object (on server)
Setup our channels
Register our object (on server)
Get a reference to object (on client)
Call method on server from the client
For example on the Server side:
public class Server {
public static void Main() {
Target t = new Target();
RemotingServices.Marshal(t,"me");
IChannel c = new HttpChannel(8080);
ChannelServices.RegisterChannel(c);
Console.WriteLine("Server ready. ");
Console.ReadLine();
}
}
147
148 CHAPTER 11. REMOTING
On the Client side we have
public class Client {
public static void Main() {
string url = "http://localhost:8080/me";
Type to = typeof(Target);
Target t = (Target) RemotingServices.Connect(to,url);
Console.WriteLine("Connected to server.");
try {
string msg = Console.ReadLine();
t.sendMessage(msg);
} catch(Exception e) {
Console.WriteLine("Error " + e.Message);
}
}
}
A shared class (i.e. it is on the Client and Server side) is
public class Target : System.MarshalByRefObject
{
public void sendMessage(string msg)
{ System.Console.WriteLine(msg); }
}
Reference types passed via remoting must be serializable or marshal-by-reference.
Serializable == pass-by-value
Copy of object is made.
MarshalByRefObject == pass-by-reference
proxy object is passed.
An example for the second case is:
public class Counter : MarshalByRefObject {
int count = 0;
public void incCount() { count++; }
public int getCount() { return count; }
}
An example for the first case is:
11.1. INTRODUCTION 149
[Serializable]
public class Counter {
int count = 0;
public void incCount() { count++; }
public int getCount() { return count; }
}
We have to import:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http; // for Http
using System.Runtime.Remoting.Channels.Tcp; // for Tcp
Shared objects must be available to both the client and the server. Make a separate
assembly for it
csc /t:library Shared.cs
csc /r:Shared.dll Server.cs
csc /r:Shared.dll Client.cs
There are three main items in the process of remoting. The Interface, the Client
and the Server. The Interface is a .dll file (generated from a .cs file) which is on
the Client and Server side. The Server provides an implementation of the Interface
and makes it available to call. The Client calls the Server using the Interface.
150 CHAPTER 11. REMOTING
Example. The client provides a string to the server and the server returns the
length of the string.
We first create an interface with the file MyInterface.cs.
// MyInterface.cs
public interface MyInterface
{
int FunctionOne(string str);
}
MyInterface.cs is on the Server and Client side. We compile to MyInterface.dll
on the Client and Server side with
csc /t:library MyInterface.cs
On the Server side we now create our class which we invoke from a remote machine
(client). RemoteObject.cs is on the Server side and is the implementation of
MyInterface.cs.
// RemoteObject.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
public class MyRemoteClass : MarshalByRefObject, MyInterface
{
public int FunctionOne(string str)
{
return str.Length; // length of string
}
}
Any class derived from MarshalByRefObject allows remote clients to invoke its
methods. Now on the Server side we compile
csc /t:library /r:MyInterface.dll RemoteObject.cs
This creates the file RemoteObject.dll on the Server side.
11.1. INTRODUCTION 151
Next we provide our Server on the Server side.
// ServerTcp.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class MyServer
{
public static void Main()
{
TcpChannel m_TcpChan = new TcpChannel(9999);
ChannelServices.RegisterChannel(m_TcpChan,false);
RemotingConfiguration.RegisterWellKnownServiceType(
Type.GetType("MyRemoteClass,RemoteObject"),
"FirstRemote",WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press ENTER to quit");
System.Console.ReadLine();
} // end Main
} // end class MyServer
First we create a TcpChannel object which we use to transport messages across our
remoting boundary. We select 9999 as the TCP port to listen on. This could cause
problems with firewalls. We use
ChannelServices.RegisterChannel
to register our TcpChannel with the channel services. Then we use
RemoteConfiguration.RegisterWellKnownServiceType
to register our RemoteClass object as a well-known type. Now this object can be
remoted.
We are using the string FirstRemote here which will be used as part of the URL
that the client uses to access the remote object. We use SingleCall mode here
which means a new instance is created for each remote call.
Remark. One could have also used Singleton in which case one object would have
been instantiated and used by all connecting clients.
Next we generate the exe file for ServerTcp via
csc /r:RemoteObject.dll ServerTcp.cs
152 CHAPTER 11. REMOTING
Now we write our Client program ClientTcp.cs on the client side.
// ClientTcp.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class MyClient
{
public static void Main()
{
TcpChannel m_TcpChan = new TcpChannel();
ChannelServices.RegisterChannel(m_TcpChan,false);
MyInterface m_RemoteObject =
(MyInterface) Activator.GetObject(typeof(MyInterface),
"tcp://192.168.0.3:9999/FirstRemote");
Console.WriteLine(m_RemoteObject.FunctionOne("willi hans steeb"));
// for LocalHost
// "tcp://LocalHost:9999/FirstRemote");
} // end Main
} // end class MyClient
Just as in the Server we have a TcpChannel object, though in this case we do not
have to specify a port. We also use
ChannelService.RegisterChannel
to register the channel. We use
Activator.GetObject
to obtain an instance of the MyInterface object. The IP address of the remote
machine (Server) is 192.168.0.3 with the port 9999. The string FirstRemote
which forms part of the URL was that we passed to
RemotingConfiguration.RegisterWellKnownServiceType
on the Server.
To obtain the ClientTcp.exe file we run
csc /r:MyInterface.dll ClientTcp.cs
11.1. INTRODUCTION 153
To run everything on the Server side we enter the exe file at the command line
ServerTcp
Then on the Client side we enter the exe file at the command line
ClientTcp
Then we are provide with the length of the string. In the present case 16.
154 CHAPTER 11. REMOTING
Instead of using Tcp we can also use Http. Then the files ServerTcp.cs and
ClientTcp.cs are replaced by ServerHttp.cs and ClientHttp.cs shown below.
// ServerHttp.cs
// on Server side
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class MyServer
{
public static void Main()
{
HttpChannel chan = new HttpChannel(8080);
ChannelServices.RegisterChannel(chan,false);
RemotingConfiguration.RegisterWellKnownServiceType(
Type.GetType("MyRemoteClass,RemoteObject"),
"FirstRemote",WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press ENTER to quit");
System.Console.ReadLine();
} // end Main
} // end class MyServer
11.1. INTRODUCTION 155
// Client.cs
// on client side
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class MyClient
{
public static void Main()
{
HttpChannel chan = new HttpChannel();
ChannelServices.RegisterChannel(chan,false);
MyInterface m_RemoteObject =
(MyInterface) Activator.GetObject(typeof(MyInterface),
"http://152.106.41.42:8080/FirstRemote");
Console.WriteLine(m_RemoteObject.FunctionOne("willi hans"));
// for LocalHost
// "tcp://LocalHost:9999/FirstRemote");
} // end Main
} // end class MyClient
programs on user datagram protocol
10.3 User Datagram Protocol
UDP is not very reliable (but fast) connectionless protocol.
Example. The UDPServer1.cs file is given by
// UDPServer1.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Configuration;
class ServerUDP
{
public static void Main()
{
UdpClient udpc = new UdpClient(2055);
Console.WriteLine("Server started servicing on port 2055");
IPEndPoint ep = null;
while(true)
{
byte[] rdata = udpc.Receive(ref ep);
string name = Encoding.ASCII.GetString(rdata);
string job = ConfigurationManager.AppSettings[name];
if(job==null) job = "No such employee";
byte[] sdata = Encoding.ASCII.GetBytes(job);
udpc.Send(sdata,sdata.Length,ep);
} // end while
} // end Main
}
The xml-file UDPServer1.exe.config is given by
146 CHAPTER 10. SOCKETS PROGRAMMING
The UDPClient1.cs file is given by
// UDPClient1.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class ClientUDP
{
public static void Main()
{
UdpClient udpc = new UdpClient("152.106.40.84",2055);
IPEndPoint ep = null;
while(true)
{
Console.Write("Name: ");
string name = Console.ReadLine();
if(name == "") break;
byte[] sdata = Encoding.ASCII.GetBytes(name);
udpc.Send(sdata,sdata.Length);
byte[] rdata = udpc.Receive(ref ep);
string job = Encoding.ASCII.GetString(rdata);
Console.WriteLine("job = " + job);
} // end while
} // end Main
}
We include an xml-file (UDPServer1.exe.config) on the Server side we can query
from the Client. On the Server side we first compile
csc UDPServer1.cs
Next we compile the Client side
csc UDPClient1.cs
Then on the Server side we start running the exe-file UDPServer1 and finally we
start the exe-file UDPClient1 on the Client side.
UDP is not very reliable (but fast) connectionless protocol.
Example. The UDPServer1.cs file is given by
// UDPServer1.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Configuration;
class ServerUDP
{
public static void Main()
{
UdpClient udpc = new UdpClient(2055);
Console.WriteLine("Server started servicing on port 2055");
IPEndPoint ep = null;
while(true)
{
byte[] rdata = udpc.Receive(ref ep);
string name = Encoding.ASCII.GetString(rdata);
string job = ConfigurationManager.AppSettings[name];
if(job==null) job = "No such employee";
byte[] sdata = Encoding.ASCII.GetBytes(job);
udpc.Send(sdata,sdata.Length,ep);
} // end while
} // end Main
}
The xml-file UDPServer1.exe.config is given by
146 CHAPTER 10. SOCKETS PROGRAMMING
The UDPClient1.cs file is given by
// UDPClient1.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class ClientUDP
{
public static void Main()
{
UdpClient udpc = new UdpClient("152.106.40.84",2055);
IPEndPoint ep = null;
while(true)
{
Console.Write("Name: ");
string name = Console.ReadLine();
if(name == "") break;
byte[] sdata = Encoding.ASCII.GetBytes(name);
udpc.Send(sdata,sdata.Length);
byte[] rdata = udpc.Receive(ref ep);
string job = Encoding.ASCII.GetString(rdata);
Console.WriteLine("job = " + job);
} // end while
} // end Main
}
We include an xml-file (UDPServer1.exe.config) on the Server side we can query
from the Client. On the Server side we first compile
csc UDPServer1.cs
Next we compile the Client side
csc UDPClient1.cs
Then on the Server side we start running the exe-file UDPServer1 and finally we
start the exe-file UDPClient1 on the Client side.
programs on sockets
Sockets Programming
10.1 Introduction
Most interprocess communication uses the client server model. These terms refer to
the two processes which will communicating with each other. One of the processes,
the client, connects to the other process, the server, typically to make a request for
information. A good analogy is a person who makes a phone call to another person.
The client needs to know of the existence of and the address of the server, but the
server does not need to know the address of (or even the existence of) the client
prior to the connection being established. Once a connection is established, both
sides can send and receive information.
The system calls for establishing a connection are different for the client and the
server, but both involve the basic construct of a socket. A socket is one end of an
interprocess communication channel. The two processes each establish their own
socket.
The steps for establishing a socket on the client side are:
1. Create a socket
2. Connect the socket to the address of the server
3. Send and receive data
The steps for establishing a socket on the server side are:
1. Create a socket
2. Bind the socket to an address. For the server socket on the Internet, an address
consists of a port number on the host machine.
3. Listen for connections.
4. Accept a connection. This call typically blocks until a client connects with the
server.
137
138 CHAPTER 10. SOCKETS PROGRAMMING
5. Send and receive data.
Thus a socket is a communication mechanism. A socket is normally identified by an
integer which may be called the socket descriptor. The socket mechanism was first
introduced in the 4.2 BSD Unix system in 1983 in conjunction with the TCP/IP
protocols that first appeared in the 4.1 BSD Unix system in late 1981.
Thus besides the IPAddress we also need a port number (2 bytes) which is arbitrary
except for the well know port numbers associated with popular applications.
Formally a socket is defined by a group of four numbers. There are
1) The remote host identification number or address (IPAddress)
2) The remote host port number
3) The local host identification number or address (IPAddress)
4) The local host port number
10.2 Transmission Control Protocol
TCP is reliable connection oriented protocol.
Example 1. When the server program is run it will indicate at which IPAddress
it is running and the port it is listening to. Now run the client program so that we
establish a connection with the server. When the connection is established the server
will display the IPAddress and port from where it has accepted the connection. The
client will ask for the string which is to be transmitted to the server. The server on
reciept of the string will display it, send an acknowledgement which will be received
by the client.
10.2. TRANSMISSION CONTROL PROTOCOL 139
// Server1.cs
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
class Server1
{
public static void Main()
{
try
{
// use local IP address and use the same in the client
IPAddress ipAd = IPAddress.Parse("152.106.40.84");
TcpListener listener = new TcpListener(ipAd,2055);
// start listening at the specified port 2055
listener.Start();
Console.WriteLine("Server is running at port 2055...");
Console.WriteLine("The local End point is: " + listener.LocalEndpoint);
Console.WriteLine("Waiting for a connection...");
Socket s = listener.AcceptSocket();
Console.WriteLine("Connection accepted from " + s.RemoteEndPoint);
byte[] b = new byte[100];
int k = s.Receive(b);
Console.WriteLine("Received...");
for(int i=0;i
Console.Write(Convert.ToChar(b[i]));
ASCIIEncoding asen = new ASCIIEncoding();
s.Send(asen.GetBytes("The string was received by the server."));
Console.WriteLine("\nSent Acknowledgement");
// clean up
s.Close();
listener.Stop();
} // end try
catch(Exception e)
{
Console.WriteLine("Error... " + e.StackTrace);
} // end catch
} // end Main
} // end class Server1
140 CHAPTER 10. SOCKETS PROGRAMMING
// Client1.cs
using System;
using System.IO;
using System.Text;
using System.Net;
using System.Net.Sockets;
class Client1
{
public static void Main()
{
try
{
TcpClient tcpclnt = new TcpClient();
Console.WriteLine("Connecting...");
tcpclnt.Connect("152.106.40.84",2055);
Console.WriteLine("Connected");
Console.Write("Enter the string to be transmitted: ");
String str = Console.ReadLine();
Stream stm = tcpclnt.GetStream();
ASCIIEncoding asen = new ASCIIEncoding();
byte[] ba = asen.GetBytes(str);
Console.WriteLine("Transmitting...");
stm.Write(ba,0,ba.Length);
byte[] bb = new byte[100];
int k = stm.Read(bb,0,100);
for(int i=0;i
Console.Write(Convert.ToChar(bb[i]));
// clean up
tcpclnt.Close();
} // end try
catch(Exception e)
{
Console.WriteLine("Error... " + e.StackTrace);
} // end catch
} // end Main
} // end class Client1
10.2. TRANSMISSION CONTROL PROTOCOL 141
Example 2. In our second example we include an xml-file (Server2.exe.config)
on the Server side we can query from the Client. On the Server side we first compile
csc /D:LOG Server2.cs
The code between #if LOG and endif will be added by the compiler only if the symbol
LOG is defined during compilation (conditional compilation). Next we compile
the Client side
csc Client2.cs
Then on the Server side we start running the exe-file Server2 and finally we start
the exe-file Client2 on the Client side.
// Server2.cs
using System;
using System.Threading;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Configuration;
class EmployeeTCPServer
{
static TcpListener listener;
const int LIMIT = 5; // 5 concurrent clients
public static void Main()
{
IPAddress ipAd = IPAddress.Parse("152.106.40.84");
listener = new TcpListener(ipAd,2055);
listener.Start();
#if LOG
Console.WriteLine("Server mounted,listening to port 2055");
#endif
for(int i=0;i
{
Thread t = new Thread(new ThreadStart(Service));
t.Start();
} // end for loop
} // end Main
public static void Service()
{
while(true)
142 CHAPTER 10. SOCKETS PROGRAMMING
{
Socket soc = listener.AcceptSocket();
#if LOG
Console.WriteLine("Connected: {0}",soc.RemoteEndPoint);
#endif
try
{
Stream s = new NetworkStream(soc);
StreamReader sr = new StreamReader(s);
StreamWriter sw = new StreamWriter(s);
sw.AutoFlush = true; // enable automatic flushing
sw.WriteLine("{0} Employees available",ConfigurationManager.AppSettings.Count);
while(true)
{
string name = sr.ReadLine();
if(name == ""
name == null) break;
string job = ConfigurationManager.AppSettings[name];
if(job == null) job = "No such employee";
sw.WriteLine(job);
} // end while
s.Close();
} // end try
catch(Exception e)
{
#if LOG
Console.WriteLine(e.Message);
#endif
} // end catch
#if LOG
Console.WriteLine("Disconnected: {0}",soc.RemoteEndPoint);
#endif
soc.Close();
}
}
} // end class Server2
10.2. TRANSMISSION CONTROL PROTOCOL 143
The file (xml-file) Server2.exe.config is given by
10.1 Introduction
Most interprocess communication uses the client server model. These terms refer to
the two processes which will communicating with each other. One of the processes,
the client, connects to the other process, the server, typically to make a request for
information. A good analogy is a person who makes a phone call to another person.
The client needs to know of the existence of and the address of the server, but the
server does not need to know the address of (or even the existence of) the client
prior to the connection being established. Once a connection is established, both
sides can send and receive information.
The system calls for establishing a connection are different for the client and the
server, but both involve the basic construct of a socket. A socket is one end of an
interprocess communication channel. The two processes each establish their own
socket.
The steps for establishing a socket on the client side are:
1. Create a socket
2. Connect the socket to the address of the server
3. Send and receive data
The steps for establishing a socket on the server side are:
1. Create a socket
2. Bind the socket to an address. For the server socket on the Internet, an address
consists of a port number on the host machine.
3. Listen for connections.
4. Accept a connection. This call typically blocks until a client connects with the
server.
137
138 CHAPTER 10. SOCKETS PROGRAMMING
5. Send and receive data.
Thus a socket is a communication mechanism. A socket is normally identified by an
integer which may be called the socket descriptor. The socket mechanism was first
introduced in the 4.2 BSD Unix system in 1983 in conjunction with the TCP/IP
protocols that first appeared in the 4.1 BSD Unix system in late 1981.
Thus besides the IPAddress we also need a port number (2 bytes) which is arbitrary
except for the well know port numbers associated with popular applications.
Formally a socket is defined by a group of four numbers. There are
1) The remote host identification number or address (IPAddress)
2) The remote host port number
3) The local host identification number or address (IPAddress)
4) The local host port number
10.2 Transmission Control Protocol
TCP is reliable connection oriented protocol.
Example 1. When the server program is run it will indicate at which IPAddress
it is running and the port it is listening to. Now run the client program so that we
establish a connection with the server. When the connection is established the server
will display the IPAddress and port from where it has accepted the connection. The
client will ask for the string which is to be transmitted to the server. The server on
reciept of the string will display it, send an acknowledgement which will be received
by the client.
10.2. TRANSMISSION CONTROL PROTOCOL 139
// Server1.cs
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
class Server1
{
public static void Main()
{
try
{
// use local IP address and use the same in the client
IPAddress ipAd = IPAddress.Parse("152.106.40.84");
TcpListener listener = new TcpListener(ipAd,2055);
// start listening at the specified port 2055
listener.Start();
Console.WriteLine("Server is running at port 2055...");
Console.WriteLine("The local End point is: " + listener.LocalEndpoint);
Console.WriteLine("Waiting for a connection...");
Socket s = listener.AcceptSocket();
Console.WriteLine("Connection accepted from " + s.RemoteEndPoint);
byte[] b = new byte[100];
int k = s.Receive(b);
Console.WriteLine("Received...");
for(int i=0;i
Console.Write(Convert.ToChar(b[i]));
ASCIIEncoding asen = new ASCIIEncoding();
s.Send(asen.GetBytes("The string was received by the server."));
Console.WriteLine("\nSent Acknowledgement");
// clean up
s.Close();
listener.Stop();
} // end try
catch(Exception e)
{
Console.WriteLine("Error... " + e.StackTrace);
} // end catch
} // end Main
} // end class Server1
140 CHAPTER 10. SOCKETS PROGRAMMING
// Client1.cs
using System;
using System.IO;
using System.Text;
using System.Net;
using System.Net.Sockets;
class Client1
{
public static void Main()
{
try
{
TcpClient tcpclnt = new TcpClient();
Console.WriteLine("Connecting...");
tcpclnt.Connect("152.106.40.84",2055);
Console.WriteLine("Connected");
Console.Write("Enter the string to be transmitted: ");
String str = Console.ReadLine();
Stream stm = tcpclnt.GetStream();
ASCIIEncoding asen = new ASCIIEncoding();
byte[] ba = asen.GetBytes(str);
Console.WriteLine("Transmitting...");
stm.Write(ba,0,ba.Length);
byte[] bb = new byte[100];
int k = stm.Read(bb,0,100);
for(int i=0;i
Console.Write(Convert.ToChar(bb[i]));
// clean up
tcpclnt.Close();
} // end try
catch(Exception e)
{
Console.WriteLine("Error... " + e.StackTrace);
} // end catch
} // end Main
} // end class Client1
10.2. TRANSMISSION CONTROL PROTOCOL 141
Example 2. In our second example we include an xml-file (Server2.exe.config)
on the Server side we can query from the Client. On the Server side we first compile
csc /D:LOG Server2.cs
The code between #if LOG and endif will be added by the compiler only if the symbol
LOG is defined during compilation (conditional compilation). Next we compile
the Client side
csc Client2.cs
Then on the Server side we start running the exe-file Server2 and finally we start
the exe-file Client2 on the Client side.
// Server2.cs
using System;
using System.Threading;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Configuration;
class EmployeeTCPServer
{
static TcpListener listener;
const int LIMIT = 5; // 5 concurrent clients
public static void Main()
{
IPAddress ipAd = IPAddress.Parse("152.106.40.84");
listener = new TcpListener(ipAd,2055);
listener.Start();
#if LOG
Console.WriteLine("Server mounted,listening to port 2055");
#endif
for(int i=0;i
{
Thread t = new Thread(new ThreadStart(Service));
t.Start();
} // end for loop
} // end Main
public static void Service()
{
while(true)
142 CHAPTER 10. SOCKETS PROGRAMMING
{
Socket soc = listener.AcceptSocket();
#if LOG
Console.WriteLine("Connected: {0}",soc.RemoteEndPoint);
#endif
try
{
Stream s = new NetworkStream(soc);
StreamReader sr = new StreamReader(s);
StreamWriter sw = new StreamWriter(s);
sw.AutoFlush = true; // enable automatic flushing
sw.WriteLine("{0} Employees available",ConfigurationManager.AppSettings.Count);
while(true)
{
string name = sr.ReadLine();
if(name == ""
name == null) break;
string job = ConfigurationManager.AppSettings[name];
if(job == null) job = "No such employee";
sw.WriteLine(job);
} // end while
s.Close();
} // end try
catch(Exception e)
{
#if LOG
Console.WriteLine(e.Message);
#endif
} // end catch
#if LOG
Console.WriteLine("Disconnected: {0}",soc.RemoteEndPoint);
#endif
soc.Close();
}
}
} // end class Server2
10.2. TRANSMISSION CONTROL PROTOCOL 143
The file (xml-file) Server2.exe.config is given by
programs on processes and threads
Processes and Threads
9.1 Processes
A process in its simplest form is a running application. The Process class provides
access to local and remote processes and enables us to start and stop local system
processes.
The Process.Start() method starts a process resource by specifying the name of
a document or application file and associates the resource with a new process component.
Thus we can start other applications from within our application.
The program below launches notepad.exe, winhlp32.exe and asynchronously counts
up from 0 to 100001, then destroys the notepad.exe process using the Kill()
method. It counts again from 0 to 2000 and then kills the winhlp32.exe process.
Each process has an Id number.
// process1.cs
using System;
using System.Diagnostics;
class Processtest
{
public static void Main()
{
Process p = Process.Start("notepad.exe");
string name = p.ProcessName;
int id = p.Id;
DateTime started = p.StartTime;
Console.WriteLine("name = " + name);
Console.WriteLine("id = " + id);
Console.WriteLine("StartTime = " + started);
114
9.2. THREADS 115
Process w = Process.Start("winhlp32.exe");
for(int i=0;i<100001;i++) Console.WriteLine(i);
p.Kill();
for(int j=0;j<2000;j++) Console.WriteLine(j);
w.Kill();
}
}
If we also want to load a file (for example process2.cs) with notepad.exe we
change this line to
Process p = Process.Start("notepad.exe","c:\\csharp\\process2.cs");
The detect process completion we use the method
WaitForExit()
9.2 Threads
9.2.1 Introduction
A thread is an execution stream within a process. A thread is also called a lightweight
process. It has it own execution stack, local variables, and program counter. There
may be more than one thread in a process. Threads are part of the same process
which execute concurrently. In .NET Base Class Library (BCL) the System.Threading
namespace provides various classes for executing and controlling threads.
The following program Threads0.cs is the simple example. We create a Thread
instance by passing it an object of ThreadStart delegate which contains a reference
to our ThreadJob method. Thus the code creates a new thread which runs the
ThreadJob method, and starts it. That thread counts from 0 to 1 while the main
thread counts from 0 to 5.
// Threads0.cs
using System;
using System.Threading;
public class Threads0
{
static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
116 CHAPTER 9. PROCESSES AND THREADS
thread.Start();
int i = 0;
while(i < 6)
{
Console.WriteLine("Main thread: {0}",i);
i++;
} // end while
} // end Main
static void ThreadJob()
{
int i = 0;
while(i < 2)
{
Console.WriteLine("Other thread: {0}",i);
i++;
} // end while
} // end ThreadJob
}
An output could be
Main thread: 0
Main thread: 1
Main thread: 2
Main thread: 3
Main thread: 4
Main thread: 5
Other thread: 0
Other thread: 1
At another run the output could be
Other thread: 0
Other thread: 1
Main thread: 0
Main thread: 1
Main thread: 2
Main thread: 3
Main thread: 4
Main thread: 5
9.2.2 Background Thread
In the program threadtest2.cs the Thread t is set to the background thread
9.2. THREADS 117
t.IsBackgound = true;
The thread will terminate when the application terminates.
// threadtest2.cs
using System;
using System.Threading; // for Thread class
class ThreadTest2
{
public static void SayHello()
{
for(int i=1;;i++)
{
Console.WriteLine("Hello {0}",i);
}
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(SayHello));
t.IsBackground = true;
t.Start();
for(int i=1;i<1001;i++)
{
Console.WriteLine("Bye {0}",i);
}
}
}
9.2.3 Sleep Method
Once a thread has been started it is often useful for that thread to pause for a
fixed period of time. Calling Thread.Sleep causes the thread to immediately
block for a fixed number of milliseconds. The Sleep method takes as a parameter
a timeout, which is the number of milliseconds that the thread should remain
blocked. The Sleep method is called when a thread wants to put itself to
sleep. One thread cannot call Sleep on another thread. Calling Thread.Sleep(0)
causes a thread to yield the remainder of its timeslice to another thread. Calling
Thread.Sleep(Timeout.Infinite) causes a thread to sleep until it is interrupted
by another thread that calls Thread.Interrupt. A thread can also be paused
by calling Thread.Suspend. When a thread calls Thread.Suspend on itself, the
call blocks until the thread is resumed by another thread. When one thread calls
Thread.Suspend on another thread, the call is a non-blocking call that causes the
118 CHAPTER 9. PROCESSES AND THREADS
other thread to pause.
The following program Threads0.cs is the simple example. We create a Thread
instance by passing it an object of ThreadStart delegate which contains a reference
to our ThreadJob method. Thus the code creates a new thread which runs the
ThreadJob method, and starts it. That thread counts from 0 to 9 fairly fast (about
twice a second) while the main thread counts from 0 to 4 fairly slowly (about once a
second). The way they count at different speeds is by each of them including a call
to Thread.Sleep(), which just makes the current thread sleep (do nothing) for the
specified period of time (milliseconds). Between each count in the main thread we
sleep for 1000ms, and between each count in the other thread we sleep for 500ms.
// ThreadsSleep.cs
using System;
using System.Threading;
public class ThreadsSleep
{
static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
thread.Start();
int i = 0;
while(i < 5)
{
Console.WriteLine("Main thread: {0}",i);
Thread.Sleep(1000);
i++;
} // end while
} // end Main
static void ThreadJob()
{
int i = 0;
while(i < 10)
{
Console.WriteLine("Other thread: {0}",i);
Thread.Sleep(500);
i++;
} // end while
} // end ThreadJob
}
9.2. THREADS 119
A typical output could be
Main thread: 0
Other thread: 0
Other thread: 1
Main thread: 1
Other thread: 2
Other thread: 3
Other thread: 4
Main thread: 2
Other thread: 5
Main thread: 3
Other thread: 6
Other thread: 7
Main thread: 4
Other thread: 8
Other thread: 9
At another run this could change.
9.2.4 Join Methods
The method Join() blocks the calling thread until a thread terminates.
// joiningthread.cs
using System;
using System.Threading;
class JoiningThread
{
public static void Run()
{
for(int i=1;i<3;i++)
Console.WriteLine("Hello {0}",i);
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
for(int i=1;i<6;i++)
Console.WriteLine("Welcome {0}",i);
t.Join();
Console.WriteLine("Goodbye");
}
120 CHAPTER 9. PROCESSES AND THREADS
}
The output is
Welcome 1
Welcome 2
Welcome 3
Welcome 4
Welcome 5
Hello 1
Hello 2
9.3 Monitor
While one thread is in a read/increment/write operation, no other threads can try
to do the same thing. This is where monitors come in. Every object in .NET has
a monitor associated with it. A thread can enter (or acquire) a monitor only if no
other thread has currently “got” it. Once a thread has acquired a monitor, it can
acquire it more times, or exit (or release) it. The monitor is only available to other
threads again once it has exited as many times as it was entered. If a thread tries
to acquire a monitor which is owened by another thread, it will block until it is able
to acquire it. There may be more than one thread trying to acquire the monitor,
in which case when the current owner thread releases it for the last time, only one
of the threads will acquire it - the other one will have to wait for the new owner to
release it too. The Pulse(object) method notifies a thread in the waiting queue
of a change in the locked object’s state. The method Wait(object) waits for the
Monitor Pulse.
// waitingthread.cs
using System;
using System.Threading;
class WaitingThread
{
static object obj = new object();
public static void thread1()
{
for(int i=1;i<6;i++)
Console.WriteLine("Welcome: {0}",i);
Monitor.Enter(obj);
Monitor.Pulse(obj);
Monitor.Exit(obj);
}
9.4. SYNCHRONIZATION 121
public static void thread2()
{
for(int i=1;i<15;i++)
Console.WriteLine("Hello: {0}",i);
Monitor.Enter(obj);
Monitor.Pulse(obj);
Monitor.Exit(obj);
}
public static void thread3()
{
for(int i=1;i<8;i++)
Console.WriteLine("Good Night: {0}",i);
Monitor.Enter(obj);
Monitor.Wait(obj);
Monitor.Pulse(obj);
Monitor.Exit(obj);
}
public static void Main()
{
ThreadStart job1 = new ThreadStart(thread1);
ThreadStart job2 = new ThreadStart(thread2);
ThreadStart job3 = new ThreadStart(thread3);
Thread t1 = new Thread(job1);
Thread t2 = new Thread(job2);
Thread t3 = new Thread(job3);
t1.Start();
t2.Start();
t3.Start();
}
}
If thread3 runs last after thread1 and thread2 have completed it freeze at
Good Night: 7
9.4 Synchronization
When two or more threads share a common resource access needs to be serialized in a
process called synchronization. Synchronization is done with the lock() operation.
The first program is without the lock operation. In the second program the lock
operation is introduced.
122 CHAPTER 9. PROCESSES AND THREADS
// syncthreads1.cs
using System;
using System.Threading;
class Account
{
private double balance = 5000;
public void Withdraw(double amount)
{
Console.WriteLine("WITHDRAWING {0}",amount);
if(amount > balance) throw new Exception("INSUFFICIENT FUNDS");
Thread.Sleep(10); // do other stuff
balance -= amount;
Console.WriteLine("BALANCE {0}",balance);
}
}
class SymnThread
{
static Account acc = new Account();
public static void Run()
{
acc.Withdraw(3000);
}
public static void Main()
{
new Thread(new ThreadStart(Run)).Start();
acc.Withdraw(3000);
}
}
Now we introduce the lock.
// syncthreads2.cs
using System;
using System.Threading;
class Account
{
private double balance = 5000;
9.5. DEADLOCK 123
public void Withdraw(double amount)
{
Console.WriteLine("WITHDRAWING {0}",amount);
lock(this)
{
if(amount > balance) throw new Exception("INSUFFICIENT FUNDS");
Thread.Sleep(10); // do other stuff
balance -= amount;
}
Console.WriteLine("BALANCE {0}",balance);
}
}
class SymnThread
{
static Account acc = new Account();
public static void Run()
{
acc.Withdraw(3000);
}
public static void Main()
{
new Thread(new ThreadStart(Run)).Start();
acc.Withdraw(3000);
}
}
9.5 Deadlock
The second major problem of multi-threading is that of deadlocks. Simply put, this
is when two threads each holds a monitor that the other one wants. Each blocks,
waiting for the monitor that it’s waiting for to be released - and so the monitors are
never released, and the application hangs (or at least those threads involved in the
deadlock hang). An example is given below.
// Deadlock.cs
using System;
using System.Threading;
public class Deadlock
{
124 CHAPTER 9. PROCESSES AND THREADS
static readonly object firstLock = new object();
static readonly object secondLock = new object();
static void ThreadJob()
{
Console.WriteLine("\t\t\t\tLocking firstLock");
lock(firstLock)
{
Console.WriteLine("\t\t\t\tLocked firstLock");
// Wait until we are fairly sure the first thread
// has grabbed secondlock
Thread.Sleep(1000);
Console.WriteLine("\t\t\t\tLocking secondLock");
lock(secondLock)
{
Console.WriteLine("\t\t\t\tLocked secondLock");
}
Console.WriteLine("\t\t\t\tReleased secondLock");
}
Console.WriteLine("\t\t\t\tReleased firstLock");
} // end method ThreadJob
public static void Main()
{
new Thread(new ThreadStart(ThreadJob)).Start();
// wait until we are fairly sure the other thread
// has grabbed firstlock
Thread.Sleep(500);
Console.WriteLine("Locking secondLock");
lock(secondLock)
{
Console.WriteLine("Locked secondLock");
Console.WriteLine("Locking firstLock");
lock(firstLock)
{
Console.WriteLine("Locked firstLock");
}
Console.WriteLine("Released firstLock");
}
Console.WriteLine("Released secondLock");
} // end Main
} // end class
9.6. INTERLOCKED CLASS 125
9.6 Interlocked Class
An operation is atomic if it is indivisible - in other words, nothing else can happen
in the middle. Thus with an atomic write, we cannot have another thread reading
the value half way through the write, and ending up “seeing” half of the old value
and half of the new value. Sinilarly, with an atomic read, we cannot have another
thread changing the value half way through the read, ending up with a value which
is neither the old nor the new value. For example, for a long (64 bits) on a 32 bit
machine, if one thread is changing the value from 0 to 0x0123456789ABCDEF, there
is no guarantee that another thread will not see the value as 0x0123456700000000
or 0x0000000089ABCDEF.
The Interlocked class provides a set of methods for performing atomic changes: exchanges
(optionally performing a comparison first), increments and decrements. The
Exchange and CompareExchange methods act on a variables of type int, object,
or float; the Increment and Decrement methods act on variables of type int and
long.
// interlocked.cs
using System;
using System.Threading;
public class MyInterlocked
{
static long count = 0;
public static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
thread.Start();
for(long i=0;i<5;i++)
{
Interlocked.Increment(ref count);
Console.WriteLine("I am in for loop in main");
}
thread.Join();
Console.WriteLine("final count: {0}",count);
} // end Main
static void ThreadJob()
{
long i = 0;
126 CHAPTER 9. PROCESSES AND THREADS
while(i < 5)
{
Interlocked.Increment(ref count);
Console.WriteLine("I am in ThreadJob");
i++;
}
} // end ThreadJob
}
First the for loop in Main will run to the end and then the while loop will be done.
9.7 Thread Pooling
We can use thread pooling to make much more efficient use of multiple threads,
depending on our application. Many applications use multiple threads, but often
those threads spend a great deal of time in the sleeping state waiting for an event
to occur. Other threads might enter a sleeping state and be awakend only periodically
to poll for a change or update status information before going to sleep again.
Using thread pooling provides our application with a pool of worker threads that
are managed by the system, allowing us to concentrate on application tasks rather
than thread management. An example is given below.
// ThreadsSum2.cs
using System;
using System.Threading;
class ThreadsSum
{
public static void Sum1(Object o)
{
int sum1 = 0;
int[] a1 = (int[]) o;
for(int i=0;i
{
sum1 += a1[i];
}
Console.WriteLine("sum1 = " + sum1);
}
public static void Sum2(Object o)
{
int sum2 = 0;
int[] a2 = (int[]) o;
for(int i=0;i
9.8. THREADING IN WINDOWS FORMS 127
{
sum2 += a2[i];
}
Console.WriteLine("sum2 = " + sum2);
}
public static void Main()
{
int[] a1 = { 2, 3, 4 };
int[] a2 = { 4, 5, 7 };
if(ThreadPool.QueueUserWorkItem(new WaitCallback(Sum1),a1))
Console.WriteLine("Sum1 queued");
if(ThreadPool.QueueUserWorkItem(new WaitCallback(Sum2),a2))
Console.WriteLine("Sum2 queued");
Thread.Sleep(10); // Give other threads a turn
}
}
9.8 Threading in Windows Forms
How to handle threading in a UI? There are two rules for Windows Forms:
1) Never invoke any method or property on a control created on another thread other
than Invoke, BeginInvoke, EndInvoke or CreateGraphics, and InvokeRequired.
Each control is effectively bound to a thread which runs its message pump. If we
try to access or change anything in the UI (for example changing the Text property)
from a different thread, we run a risk of our program hanging or misbehaving
in other ways. We may get away with it in some cases. Fortunately, the Invoke,
BeginInvoke and EndInvoke methods have been provided so that we can ask the
UI thread to call a method in a safe manner.
2) Never execute a long-running piece of code in the UI thread. If our code is running
in the UI thread, that means no other code is running in that thread. That means
we won’t receive events, our controls won’t be repainted, etc. We can execute longrunning
code and periodically call Application.DoEvents(). It means we have
to consider re-entrancy issues etc, which are harder to diagnose and fix than ”normal”
threading problems. We have to judge when to call DoEvents, and we can’t
use anything which might block (network access, for instance) without risking an
unresponsive UI. There are message pumping issues in terms of COM objects as well.
If we have a piece of long-running code which we need to execute, we need to create
a new thread (or use a thread pool thread if we prefer) to execute it on, and make
128 CHAPTER 9. PROCESSES AND THREADS
sure it doesn’t directly try to update the UI with its results. The thread creation
part is the same as any other threading problem. It is interesting going the other
way - invoking a method on the UI thread in order to update the UI. There are two
different ways of invoking a method on the UI thread, one synchronous (Invoke)
and one asynchronous (BeginInvoke). They work in much the same way - we specify
a delegate and (optionally) some arguments, and a message goes on the queue
for the UI thread to process. If we use Invoke, the current thread will block until
the delegate has been executed. If we use BeginInvoke, the call will return immediately.
If we need to get the return value of a delegate invoked asynchronously, we
can use EndInvoke with the IAsyncResult returned by BeginInvoke to wait until
the delegate has completed and fetch the return value.
There are two options when working out how to get information between the various
threads involved. The first option is to have state in the class itself, setting it in
one thread, retrieving and processing it in the other (updating the display in the UI
thread, for example). The second option is to pass the information as parameters
in the delegate. Using state somewhere is necessary if we are creating a new thread
rather than using the thread pool - but that doesn’t mean we have to use state to
return information to the UI. However, creating a delegate with lots of parameters
feels clumsy, and is in some ways less efficient than using a simple MethodInvoker
or EventHandler delegate. These two delegates are treated in a special (fast) manner
by Invoke and BeginInvoke. MethodInvoker is just a delegate which takes no
parameters and returns no value (like ThreadStart), and EventHandler takes two
parameters (a sender and an EventArgs parameter and returns no value. However if
we pass an EventHandler delegate to Invoke or BeginInvoke then even if we specify
parameters ourself, they are ignored - when the method is invoked, the sender will
be the control we have invoked it with, and the EventArgs will be EventArgs.Empty.
Here is an example which shows several of the above concepts.
// ThreadingForms.cs
using System;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;
public class Test : Form
{
delegate void StringParameterDelegate(string value);
Label statusIndicator;
Label counter;
Button button;
readonly object stateLock = new object();
9.8. THREADING IN WINDOWS FORMS 129
int target;
int currentCount;
Random rng = new Random();
Test()
{
Size = new Size(180,120);
Text = "Test";
Label lbl = new Label();
lbl.Text = "Status:";
lbl.Size = new Size(50,20);
lbl.Location = new Point(10,10);
Controls.Add(lbl);
lbl = new Label();
lbl.Text = "Count:";
lbl.Size = new Size(50,20);
lbl.Location = new Point(10,34);
Controls.Add(lbl);
statusIndicator = new Label();
statusIndicator.Size = new Size(100,20);
statusIndicator.Location = new Point(70,10);
Controls.Add(statusIndicator);
counter = new Label();
counter.Size = new Size(100,20);
counter.Location = new Point(70,34);
Controls.Add(counter);
button = new Button();
button.Text = "Run";
button.Size = new Size(50,20);
button.Location = new Point(10,58);
Controls.Add(button);
button.Click += new EventHandler(StartThread);
}
void StartThread(object sender,EventArgs e)
{
button.Enabled = false;
lock(stateLock)
{
130 CHAPTER 9. PROCESSES AND THREADS
target = rng.Next(100);
}
Thread t = new Thread(new ThreadStart(ThreadJob));
t.IsBackground = true;
t.Start();
}
void ThreadJob()
{
MethodInvoker updateCounterDelegate = new MethodInvoker(UpdateCount);
int localTarget;
lock(stateLock)
{
localTarget = target;
}
UpdateStatus("Starting");
lock(stateLock)
{
currentCount = 0;
}
Invoke(updateCounterDelegate);
// Pause before starting
Thread.Sleep(500);
UpdateStatus("Counting");
for(int i=0;i
{
lock(stateLock)
{
currentCount = i;
}
// Synchronously show the counter
Invoke(updateCounterDelegate);
Thread.Sleep(100);
}
UpdateStatus("Finished");
Invoke(new MethodInvoker(EnableButton));
}
void UpdateStatus(string value)
{
if(InvokeRequired)
{
// We’re not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new StringParameterDelegate(UpdateStatus),new object[]{value});
9.8. THREADING IN WINDOWS FORMS 131
return;
}
// Must be on the UI thread if we’ve got this far
statusIndicator.Text = value;
}
void UpdateCount()
{
int tmpCount;
lock(stateLock)
{
tmpCount = currentCount;
}
counter.Text = tmpCount.ToString();
}
void EnableButton()
{
button.Enabled = true;
}
static void Main()
{
Application.Run(new Test());
}
}
State is used to tell the worker thread what number to count up to. A delegate taking
a parameter is used to ask the UI to update the status label. The worker thread’s
principal method actually just calls UpdateStatus, which uses InvokeRequired
to detect whether or not it needs to ”change thread”. If it does, it then calls
BeginInvoke to execute the same method again from the UI thread. This is quite
a common way of making a method which interacts with the UI thread-safe. The
choice of BeginInvoke rather than Invoke here was just to demonstrate how to
invoke a method asynchronously. In real code, we would decide based on whether
we needed to block to wait for the access to the UI to complete before continuing
or not. It is quite rare to actually require UI access to complete first, so we should
use BeginInvoke instead of Invoke. Another approach might be to have a property
which did the appropriate invoking when necessary. It is easier to use from the
client code, but slightly harder work in that we would either have to have another
method anyway, or get the MethodInfo for the property setter in order to construct
the delegate to invoke. In this case we actually know that BeginInvoke is required
because we are running in the worker thread anyway. We do not call EndInvoke after
the BeginInvoke. Unlike all other asynchronous methods we do not need to call
EndInvoke unless we need the return value of the delegate’s method. BeginInvoke
132 CHAPTER 9. PROCESSES AND THREADS
is also different to all of the other asynchronous methods as it doesn’t cause the
delegate to be run on a thread pool thread. State is used again to tell the UI thread
how far we have counted so far. We use a MethodInvoker delegate to execute
UpdateCount. We call this using Invoke to make sure that it executes on the UI
thread. This time there’s no attempt to detect whether or not an Invoke is required.
If we call BeginInvoke it will have a different effect than calling the method directly
as it will occur later, rather than in the current execution flow, of course. Again,
we actually know that we need to call Invoke here anyway. A button is provided to
let the user start the thread. It is disabled while the thread is running, and another
MethodInvoker delegate is used to enable the button again afterwards. All state
which is shared between threads (the current count and the target) is accessed in
locks in the way described earlier. We spend as little time as possible in the lock, not
updating the UI or anything else while holding the lock. This probably doesn’t make
too much difference here. It would be disastrous to still have the lock in the worker
thread when synchronously invoking UpdateCount - the UI thread would then try
to acquire the lock as well, and we end up with deadlock. The worker thread is
set to be a background thread (IsBackground=true;) so that when the UI thread
exits, the whole application finishes. In other cases where we have a thread which
should keep running even after the UI thread has quit, we need to be careful not
to call Invoke or BeginInvoke when the UI thread is no longer running - we will
either block permanently (waiting for the message to be taken off the queue, with
nothing actually looking at messages) or receive an exception.
9.9 Asynchronous Programming Model
When a caller invokes a method, the call is synchronous that is the caller has to
wait for the method to return before the remaining code can be executed. .NET has
an inbuilt support for asynchronous method invocation. Here the caller can issue a
request for invocation of a method and concurreently execute the remaining code.
For every delegate declared in an assembly the compiler emits a class (subclass of
System.MulticastDelegate) with Invoke, BeginInvoke and EndInvoke methods.
For example, consider a delegate declared as
delegate int MyWorker(char c,int m);
The compiler will emit the MyWorker class
class MyWorker : System.MulticastDelegate
{
public int Invoke(char c,int m);
public System.IAsyncResult BeginInvoke(char c,int m,System.AsyncCallback cb,
object asyncState);
public int EndInvoke(System.IAsyncResult result);
}
9.10. TIMERS 133
The BeginInvoke and EndInvoke methods can be used for asynchronous invocation
of a method pointed by MyWorker delegate. In the program below the method
DoWork (it displays character c m number of times) is invoked asynchronously with
character ’+’, in the next statement this method is directly invoked with character
’*’. Both ’+’ and ’*’ are displayed (1000 times each) on the Console simultaneously.
As an asynchronous call executes within its own background thread, we have
used Console.Read() to pause the main thread.
// Asynchronous.cs
using System;
delegate int MyWorker(char c,int m);
class AsncTest
{
static MyWorker worker;
static int DoWork(char c,int m)
{
int t = Environment.TickCount; // returns number of milliseconds
// elapsed since the system started
for(int i=1;i<=m;i++) Console.Write(c);
return (Environment.TickCount - t);
}
public static void Main()
{
Console.WriteLine("Start");
worker = new MyWorker(DoWork);
worker.BeginInvoke(’+’,1000,null,null); // asynchronous call
DoWork(’*’,1000); // synchronous call
Console.Read(); // pause until user presses a key
}
}
9.10 Timers
There are various different timers available in .NET, each of which basically calls
a delegate after a certain amount of time has passed. All the timers implement
IDispossible. so we have to make sure to dispose when we are not using them
anymore.
// Timers.cs
134 CHAPTER 9. PROCESSES AND THREADS
using System;
using System.Threading;
public class Timers
{
public static void Main()
{
Console.WriteLine("Started at {0:HH:mm:ss.fff}",DateTime.Now);
// Start in three seconds, then fire every second
using(Timer timer = new Timer(new TimerCallback(Tick),null,3000,1000))
{
// wait for 10 seconds
Thread.Sleep(10000);
// then go slow for another 10 seconds
timer.Change(0,2000);
Thread.Sleep(10000);
}
// the timerm will now have been disposed automatically due
// to the using statement so there won’t be any other threads running
} // end Main()
static void Tick(object state)
{
Console.WriteLine("Ticked at {0:HH:mm:ss.fff}",DateTime.Now);
}
}
9.11 Interrupt and Abort
There are two methods in the Thread class which are used for stopping threads -
Abort and Interrupt. Calling Thread.Abort aborts that thread as soon as possible.
Aborting a thread which is executing unmanaged code has no effect until the CLR
gets control again. Calling Thread.Interrupt is similar, but less drastic. This
causes a ThreadInterruptedException exception to be thrown the next time the
thread enters the WaitSleepJoin state, or immediately if the thread is already in
that state.
// Interruptthread.cs
using System;
using System.Threading;
class SleepingThread
{
public static void Run()
9.11. INTERRUPT AND ABORT 135
{
for(int i=1;i<6;i++)
Console.WriteLine("Welcome {0}",i);
try
{
Thread.Sleep(5000);
}
catch(ThreadInterruptedException e)
{
Console.WriteLine("Sleep Interrupted");
}
Console.WriteLine("Goodbye");
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
for(int i=1;i<16;i++)
Console.WriteLine("Hello {0}",i);
t.Interrupt();
}
}
The program threadtest1.cs uses the Abort method.
// threadtest1.cs
using System;
using System.Threading; // for Thread class
class ThreadTest1
{
public static void SayHello()
{
for(int i=1;;i++)
{
Console.WriteLine("Hello {0}",i);
}
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(SayHello));
136 CHAPTER 9. PROCESSES AND THREADS
t.Start();
for(int i=1;i<1001;i++)
{
Console.WriteLine("Bye {0}",i);
}
t.Abort();
}
}
9.1 Processes
A process in its simplest form is a running application. The Process class provides
access to local and remote processes and enables us to start and stop local system
processes.
The Process.Start() method starts a process resource by specifying the name of
a document or application file and associates the resource with a new process component.
Thus we can start other applications from within our application.
The program below launches notepad.exe, winhlp32.exe and asynchronously counts
up from 0 to 100001, then destroys the notepad.exe process using the Kill()
method. It counts again from 0 to 2000 and then kills the winhlp32.exe process.
Each process has an Id number.
// process1.cs
using System;
using System.Diagnostics;
class Processtest
{
public static void Main()
{
Process p = Process.Start("notepad.exe");
string name = p.ProcessName;
int id = p.Id;
DateTime started = p.StartTime;
Console.WriteLine("name = " + name);
Console.WriteLine("id = " + id);
Console.WriteLine("StartTime = " + started);
114
9.2. THREADS 115
Process w = Process.Start("winhlp32.exe");
for(int i=0;i<100001;i++) Console.WriteLine(i);
p.Kill();
for(int j=0;j<2000;j++) Console.WriteLine(j);
w.Kill();
}
}
If we also want to load a file (for example process2.cs) with notepad.exe we
change this line to
Process p = Process.Start("notepad.exe","c:\\csharp\\process2.cs");
The detect process completion we use the method
WaitForExit()
9.2 Threads
9.2.1 Introduction
A thread is an execution stream within a process. A thread is also called a lightweight
process. It has it own execution stack, local variables, and program counter. There
may be more than one thread in a process. Threads are part of the same process
which execute concurrently. In .NET Base Class Library (BCL) the System.Threading
namespace provides various classes for executing and controlling threads.
The following program Threads0.cs is the simple example. We create a Thread
instance by passing it an object of ThreadStart delegate which contains a reference
to our ThreadJob method. Thus the code creates a new thread which runs the
ThreadJob method, and starts it. That thread counts from 0 to 1 while the main
thread counts from 0 to 5.
// Threads0.cs
using System;
using System.Threading;
public class Threads0
{
static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
116 CHAPTER 9. PROCESSES AND THREADS
thread.Start();
int i = 0;
while(i < 6)
{
Console.WriteLine("Main thread: {0}",i);
i++;
} // end while
} // end Main
static void ThreadJob()
{
int i = 0;
while(i < 2)
{
Console.WriteLine("Other thread: {0}",i);
i++;
} // end while
} // end ThreadJob
}
An output could be
Main thread: 0
Main thread: 1
Main thread: 2
Main thread: 3
Main thread: 4
Main thread: 5
Other thread: 0
Other thread: 1
At another run the output could be
Other thread: 0
Other thread: 1
Main thread: 0
Main thread: 1
Main thread: 2
Main thread: 3
Main thread: 4
Main thread: 5
9.2.2 Background Thread
In the program threadtest2.cs the Thread t is set to the background thread
9.2. THREADS 117
t.IsBackgound = true;
The thread will terminate when the application terminates.
// threadtest2.cs
using System;
using System.Threading; // for Thread class
class ThreadTest2
{
public static void SayHello()
{
for(int i=1;;i++)
{
Console.WriteLine("Hello {0}",i);
}
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(SayHello));
t.IsBackground = true;
t.Start();
for(int i=1;i<1001;i++)
{
Console.WriteLine("Bye {0}",i);
}
}
}
9.2.3 Sleep Method
Once a thread has been started it is often useful for that thread to pause for a
fixed period of time. Calling Thread.Sleep causes the thread to immediately
block for a fixed number of milliseconds. The Sleep method takes as a parameter
a timeout, which is the number of milliseconds that the thread should remain
blocked. The Sleep method is called when a thread wants to put itself to
sleep. One thread cannot call Sleep on another thread. Calling Thread.Sleep(0)
causes a thread to yield the remainder of its timeslice to another thread. Calling
Thread.Sleep(Timeout.Infinite) causes a thread to sleep until it is interrupted
by another thread that calls Thread.Interrupt. A thread can also be paused
by calling Thread.Suspend. When a thread calls Thread.Suspend on itself, the
call blocks until the thread is resumed by another thread. When one thread calls
Thread.Suspend on another thread, the call is a non-blocking call that causes the
118 CHAPTER 9. PROCESSES AND THREADS
other thread to pause.
The following program Threads0.cs is the simple example. We create a Thread
instance by passing it an object of ThreadStart delegate which contains a reference
to our ThreadJob method. Thus the code creates a new thread which runs the
ThreadJob method, and starts it. That thread counts from 0 to 9 fairly fast (about
twice a second) while the main thread counts from 0 to 4 fairly slowly (about once a
second). The way they count at different speeds is by each of them including a call
to Thread.Sleep(), which just makes the current thread sleep (do nothing) for the
specified period of time (milliseconds). Between each count in the main thread we
sleep for 1000ms, and between each count in the other thread we sleep for 500ms.
// ThreadsSleep.cs
using System;
using System.Threading;
public class ThreadsSleep
{
static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
thread.Start();
int i = 0;
while(i < 5)
{
Console.WriteLine("Main thread: {0}",i);
Thread.Sleep(1000);
i++;
} // end while
} // end Main
static void ThreadJob()
{
int i = 0;
while(i < 10)
{
Console.WriteLine("Other thread: {0}",i);
Thread.Sleep(500);
i++;
} // end while
} // end ThreadJob
}
9.2. THREADS 119
A typical output could be
Main thread: 0
Other thread: 0
Other thread: 1
Main thread: 1
Other thread: 2
Other thread: 3
Other thread: 4
Main thread: 2
Other thread: 5
Main thread: 3
Other thread: 6
Other thread: 7
Main thread: 4
Other thread: 8
Other thread: 9
At another run this could change.
9.2.4 Join Methods
The method Join() blocks the calling thread until a thread terminates.
// joiningthread.cs
using System;
using System.Threading;
class JoiningThread
{
public static void Run()
{
for(int i=1;i<3;i++)
Console.WriteLine("Hello {0}",i);
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
for(int i=1;i<6;i++)
Console.WriteLine("Welcome {0}",i);
t.Join();
Console.WriteLine("Goodbye");
}
120 CHAPTER 9. PROCESSES AND THREADS
}
The output is
Welcome 1
Welcome 2
Welcome 3
Welcome 4
Welcome 5
Hello 1
Hello 2
9.3 Monitor
While one thread is in a read/increment/write operation, no other threads can try
to do the same thing. This is where monitors come in. Every object in .NET has
a monitor associated with it. A thread can enter (or acquire) a monitor only if no
other thread has currently “got” it. Once a thread has acquired a monitor, it can
acquire it more times, or exit (or release) it. The monitor is only available to other
threads again once it has exited as many times as it was entered. If a thread tries
to acquire a monitor which is owened by another thread, it will block until it is able
to acquire it. There may be more than one thread trying to acquire the monitor,
in which case when the current owner thread releases it for the last time, only one
of the threads will acquire it - the other one will have to wait for the new owner to
release it too. The Pulse(object) method notifies a thread in the waiting queue
of a change in the locked object’s state. The method Wait(object) waits for the
Monitor Pulse.
// waitingthread.cs
using System;
using System.Threading;
class WaitingThread
{
static object obj = new object();
public static void thread1()
{
for(int i=1;i<6;i++)
Console.WriteLine("Welcome: {0}",i);
Monitor.Enter(obj);
Monitor.Pulse(obj);
Monitor.Exit(obj);
}
9.4. SYNCHRONIZATION 121
public static void thread2()
{
for(int i=1;i<15;i++)
Console.WriteLine("Hello: {0}",i);
Monitor.Enter(obj);
Monitor.Pulse(obj);
Monitor.Exit(obj);
}
public static void thread3()
{
for(int i=1;i<8;i++)
Console.WriteLine("Good Night: {0}",i);
Monitor.Enter(obj);
Monitor.Wait(obj);
Monitor.Pulse(obj);
Monitor.Exit(obj);
}
public static void Main()
{
ThreadStart job1 = new ThreadStart(thread1);
ThreadStart job2 = new ThreadStart(thread2);
ThreadStart job3 = new ThreadStart(thread3);
Thread t1 = new Thread(job1);
Thread t2 = new Thread(job2);
Thread t3 = new Thread(job3);
t1.Start();
t2.Start();
t3.Start();
}
}
If thread3 runs last after thread1 and thread2 have completed it freeze at
Good Night: 7
9.4 Synchronization
When two or more threads share a common resource access needs to be serialized in a
process called synchronization. Synchronization is done with the lock() operation.
The first program is without the lock operation. In the second program the lock
operation is introduced.
122 CHAPTER 9. PROCESSES AND THREADS
// syncthreads1.cs
using System;
using System.Threading;
class Account
{
private double balance = 5000;
public void Withdraw(double amount)
{
Console.WriteLine("WITHDRAWING {0}",amount);
if(amount > balance) throw new Exception("INSUFFICIENT FUNDS");
Thread.Sleep(10); // do other stuff
balance -= amount;
Console.WriteLine("BALANCE {0}",balance);
}
}
class SymnThread
{
static Account acc = new Account();
public static void Run()
{
acc.Withdraw(3000);
}
public static void Main()
{
new Thread(new ThreadStart(Run)).Start();
acc.Withdraw(3000);
}
}
Now we introduce the lock.
// syncthreads2.cs
using System;
using System.Threading;
class Account
{
private double balance = 5000;
9.5. DEADLOCK 123
public void Withdraw(double amount)
{
Console.WriteLine("WITHDRAWING {0}",amount);
lock(this)
{
if(amount > balance) throw new Exception("INSUFFICIENT FUNDS");
Thread.Sleep(10); // do other stuff
balance -= amount;
}
Console.WriteLine("BALANCE {0}",balance);
}
}
class SymnThread
{
static Account acc = new Account();
public static void Run()
{
acc.Withdraw(3000);
}
public static void Main()
{
new Thread(new ThreadStart(Run)).Start();
acc.Withdraw(3000);
}
}
9.5 Deadlock
The second major problem of multi-threading is that of deadlocks. Simply put, this
is when two threads each holds a monitor that the other one wants. Each blocks,
waiting for the monitor that it’s waiting for to be released - and so the monitors are
never released, and the application hangs (or at least those threads involved in the
deadlock hang). An example is given below.
// Deadlock.cs
using System;
using System.Threading;
public class Deadlock
{
124 CHAPTER 9. PROCESSES AND THREADS
static readonly object firstLock = new object();
static readonly object secondLock = new object();
static void ThreadJob()
{
Console.WriteLine("\t\t\t\tLocking firstLock");
lock(firstLock)
{
Console.WriteLine("\t\t\t\tLocked firstLock");
// Wait until we are fairly sure the first thread
// has grabbed secondlock
Thread.Sleep(1000);
Console.WriteLine("\t\t\t\tLocking secondLock");
lock(secondLock)
{
Console.WriteLine("\t\t\t\tLocked secondLock");
}
Console.WriteLine("\t\t\t\tReleased secondLock");
}
Console.WriteLine("\t\t\t\tReleased firstLock");
} // end method ThreadJob
public static void Main()
{
new Thread(new ThreadStart(ThreadJob)).Start();
// wait until we are fairly sure the other thread
// has grabbed firstlock
Thread.Sleep(500);
Console.WriteLine("Locking secondLock");
lock(secondLock)
{
Console.WriteLine("Locked secondLock");
Console.WriteLine("Locking firstLock");
lock(firstLock)
{
Console.WriteLine("Locked firstLock");
}
Console.WriteLine("Released firstLock");
}
Console.WriteLine("Released secondLock");
} // end Main
} // end class
9.6. INTERLOCKED CLASS 125
9.6 Interlocked Class
An operation is atomic if it is indivisible - in other words, nothing else can happen
in the middle. Thus with an atomic write, we cannot have another thread reading
the value half way through the write, and ending up “seeing” half of the old value
and half of the new value. Sinilarly, with an atomic read, we cannot have another
thread changing the value half way through the read, ending up with a value which
is neither the old nor the new value. For example, for a long (64 bits) on a 32 bit
machine, if one thread is changing the value from 0 to 0x0123456789ABCDEF, there
is no guarantee that another thread will not see the value as 0x0123456700000000
or 0x0000000089ABCDEF.
The Interlocked class provides a set of methods for performing atomic changes: exchanges
(optionally performing a comparison first), increments and decrements. The
Exchange and CompareExchange methods act on a variables of type int, object,
or float; the Increment and Decrement methods act on variables of type int and
long.
// interlocked.cs
using System;
using System.Threading;
public class MyInterlocked
{
static long count = 0;
public static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
thread.Start();
for(long i=0;i<5;i++)
{
Interlocked.Increment(ref count);
Console.WriteLine("I am in for loop in main");
}
thread.Join();
Console.WriteLine("final count: {0}",count);
} // end Main
static void ThreadJob()
{
long i = 0;
126 CHAPTER 9. PROCESSES AND THREADS
while(i < 5)
{
Interlocked.Increment(ref count);
Console.WriteLine("I am in ThreadJob");
i++;
}
} // end ThreadJob
}
First the for loop in Main will run to the end and then the while loop will be done.
9.7 Thread Pooling
We can use thread pooling to make much more efficient use of multiple threads,
depending on our application. Many applications use multiple threads, but often
those threads spend a great deal of time in the sleeping state waiting for an event
to occur. Other threads might enter a sleeping state and be awakend only periodically
to poll for a change or update status information before going to sleep again.
Using thread pooling provides our application with a pool of worker threads that
are managed by the system, allowing us to concentrate on application tasks rather
than thread management. An example is given below.
// ThreadsSum2.cs
using System;
using System.Threading;
class ThreadsSum
{
public static void Sum1(Object o)
{
int sum1 = 0;
int[] a1 = (int[]) o;
for(int i=0;i
{
sum1 += a1[i];
}
Console.WriteLine("sum1 = " + sum1);
}
public static void Sum2(Object o)
{
int sum2 = 0;
int[] a2 = (int[]) o;
for(int i=0;i
9.8. THREADING IN WINDOWS FORMS 127
{
sum2 += a2[i];
}
Console.WriteLine("sum2 = " + sum2);
}
public static void Main()
{
int[] a1 = { 2, 3, 4 };
int[] a2 = { 4, 5, 7 };
if(ThreadPool.QueueUserWorkItem(new WaitCallback(Sum1),a1))
Console.WriteLine("Sum1 queued");
if(ThreadPool.QueueUserWorkItem(new WaitCallback(Sum2),a2))
Console.WriteLine("Sum2 queued");
Thread.Sleep(10); // Give other threads a turn
}
}
9.8 Threading in Windows Forms
How to handle threading in a UI? There are two rules for Windows Forms:
1) Never invoke any method or property on a control created on another thread other
than Invoke, BeginInvoke, EndInvoke or CreateGraphics, and InvokeRequired.
Each control is effectively bound to a thread which runs its message pump. If we
try to access or change anything in the UI (for example changing the Text property)
from a different thread, we run a risk of our program hanging or misbehaving
in other ways. We may get away with it in some cases. Fortunately, the Invoke,
BeginInvoke and EndInvoke methods have been provided so that we can ask the
UI thread to call a method in a safe manner.
2) Never execute a long-running piece of code in the UI thread. If our code is running
in the UI thread, that means no other code is running in that thread. That means
we won’t receive events, our controls won’t be repainted, etc. We can execute longrunning
code and periodically call Application.DoEvents(). It means we have
to consider re-entrancy issues etc, which are harder to diagnose and fix than ”normal”
threading problems. We have to judge when to call DoEvents, and we can’t
use anything which might block (network access, for instance) without risking an
unresponsive UI. There are message pumping issues in terms of COM objects as well.
If we have a piece of long-running code which we need to execute, we need to create
a new thread (or use a thread pool thread if we prefer) to execute it on, and make
128 CHAPTER 9. PROCESSES AND THREADS
sure it doesn’t directly try to update the UI with its results. The thread creation
part is the same as any other threading problem. It is interesting going the other
way - invoking a method on the UI thread in order to update the UI. There are two
different ways of invoking a method on the UI thread, one synchronous (Invoke)
and one asynchronous (BeginInvoke). They work in much the same way - we specify
a delegate and (optionally) some arguments, and a message goes on the queue
for the UI thread to process. If we use Invoke, the current thread will block until
the delegate has been executed. If we use BeginInvoke, the call will return immediately.
If we need to get the return value of a delegate invoked asynchronously, we
can use EndInvoke with the IAsyncResult returned by BeginInvoke to wait until
the delegate has completed and fetch the return value.
There are two options when working out how to get information between the various
threads involved. The first option is to have state in the class itself, setting it in
one thread, retrieving and processing it in the other (updating the display in the UI
thread, for example). The second option is to pass the information as parameters
in the delegate. Using state somewhere is necessary if we are creating a new thread
rather than using the thread pool - but that doesn’t mean we have to use state to
return information to the UI. However, creating a delegate with lots of parameters
feels clumsy, and is in some ways less efficient than using a simple MethodInvoker
or EventHandler delegate. These two delegates are treated in a special (fast) manner
by Invoke and BeginInvoke. MethodInvoker is just a delegate which takes no
parameters and returns no value (like ThreadStart), and EventHandler takes two
parameters (a sender and an EventArgs parameter and returns no value. However if
we pass an EventHandler delegate to Invoke or BeginInvoke then even if we specify
parameters ourself, they are ignored - when the method is invoked, the sender will
be the control we have invoked it with, and the EventArgs will be EventArgs.Empty.
Here is an example which shows several of the above concepts.
// ThreadingForms.cs
using System;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;
public class Test : Form
{
delegate void StringParameterDelegate(string value);
Label statusIndicator;
Label counter;
Button button;
readonly object stateLock = new object();
9.8. THREADING IN WINDOWS FORMS 129
int target;
int currentCount;
Random rng = new Random();
Test()
{
Size = new Size(180,120);
Text = "Test";
Label lbl = new Label();
lbl.Text = "Status:";
lbl.Size = new Size(50,20);
lbl.Location = new Point(10,10);
Controls.Add(lbl);
lbl = new Label();
lbl.Text = "Count:";
lbl.Size = new Size(50,20);
lbl.Location = new Point(10,34);
Controls.Add(lbl);
statusIndicator = new Label();
statusIndicator.Size = new Size(100,20);
statusIndicator.Location = new Point(70,10);
Controls.Add(statusIndicator);
counter = new Label();
counter.Size = new Size(100,20);
counter.Location = new Point(70,34);
Controls.Add(counter);
button = new Button();
button.Text = "Run";
button.Size = new Size(50,20);
button.Location = new Point(10,58);
Controls.Add(button);
button.Click += new EventHandler(StartThread);
}
void StartThread(object sender,EventArgs e)
{
button.Enabled = false;
lock(stateLock)
{
130 CHAPTER 9. PROCESSES AND THREADS
target = rng.Next(100);
}
Thread t = new Thread(new ThreadStart(ThreadJob));
t.IsBackground = true;
t.Start();
}
void ThreadJob()
{
MethodInvoker updateCounterDelegate = new MethodInvoker(UpdateCount);
int localTarget;
lock(stateLock)
{
localTarget = target;
}
UpdateStatus("Starting");
lock(stateLock)
{
currentCount = 0;
}
Invoke(updateCounterDelegate);
// Pause before starting
Thread.Sleep(500);
UpdateStatus("Counting");
for(int i=0;i
{
lock(stateLock)
{
currentCount = i;
}
// Synchronously show the counter
Invoke(updateCounterDelegate);
Thread.Sleep(100);
}
UpdateStatus("Finished");
Invoke(new MethodInvoker(EnableButton));
}
void UpdateStatus(string value)
{
if(InvokeRequired)
{
// We’re not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new StringParameterDelegate(UpdateStatus),new object[]{value});
9.8. THREADING IN WINDOWS FORMS 131
return;
}
// Must be on the UI thread if we’ve got this far
statusIndicator.Text = value;
}
void UpdateCount()
{
int tmpCount;
lock(stateLock)
{
tmpCount = currentCount;
}
counter.Text = tmpCount.ToString();
}
void EnableButton()
{
button.Enabled = true;
}
static void Main()
{
Application.Run(new Test());
}
}
State is used to tell the worker thread what number to count up to. A delegate taking
a parameter is used to ask the UI to update the status label. The worker thread’s
principal method actually just calls UpdateStatus, which uses InvokeRequired
to detect whether or not it needs to ”change thread”. If it does, it then calls
BeginInvoke to execute the same method again from the UI thread. This is quite
a common way of making a method which interacts with the UI thread-safe. The
choice of BeginInvoke rather than Invoke here was just to demonstrate how to
invoke a method asynchronously. In real code, we would decide based on whether
we needed to block to wait for the access to the UI to complete before continuing
or not. It is quite rare to actually require UI access to complete first, so we should
use BeginInvoke instead of Invoke. Another approach might be to have a property
which did the appropriate invoking when necessary. It is easier to use from the
client code, but slightly harder work in that we would either have to have another
method anyway, or get the MethodInfo for the property setter in order to construct
the delegate to invoke. In this case we actually know that BeginInvoke is required
because we are running in the worker thread anyway. We do not call EndInvoke after
the BeginInvoke. Unlike all other asynchronous methods we do not need to call
EndInvoke unless we need the return value of the delegate’s method. BeginInvoke
132 CHAPTER 9. PROCESSES AND THREADS
is also different to all of the other asynchronous methods as it doesn’t cause the
delegate to be run on a thread pool thread. State is used again to tell the UI thread
how far we have counted so far. We use a MethodInvoker delegate to execute
UpdateCount. We call this using Invoke to make sure that it executes on the UI
thread. This time there’s no attempt to detect whether or not an Invoke is required.
If we call BeginInvoke it will have a different effect than calling the method directly
as it will occur later, rather than in the current execution flow, of course. Again,
we actually know that we need to call Invoke here anyway. A button is provided to
let the user start the thread. It is disabled while the thread is running, and another
MethodInvoker delegate is used to enable the button again afterwards. All state
which is shared between threads (the current count and the target) is accessed in
locks in the way described earlier. We spend as little time as possible in the lock, not
updating the UI or anything else while holding the lock. This probably doesn’t make
too much difference here. It would be disastrous to still have the lock in the worker
thread when synchronously invoking UpdateCount - the UI thread would then try
to acquire the lock as well, and we end up with deadlock. The worker thread is
set to be a background thread (IsBackground=true;) so that when the UI thread
exits, the whole application finishes. In other cases where we have a thread which
should keep running even after the UI thread has quit, we need to be careful not
to call Invoke or BeginInvoke when the UI thread is no longer running - we will
either block permanently (waiting for the message to be taken off the queue, with
nothing actually looking at messages) or receive an exception.
9.9 Asynchronous Programming Model
When a caller invokes a method, the call is synchronous that is the caller has to
wait for the method to return before the remaining code can be executed. .NET has
an inbuilt support for asynchronous method invocation. Here the caller can issue a
request for invocation of a method and concurreently execute the remaining code.
For every delegate declared in an assembly the compiler emits a class (subclass of
System.MulticastDelegate) with Invoke, BeginInvoke and EndInvoke methods.
For example, consider a delegate declared as
delegate int MyWorker(char c,int m);
The compiler will emit the MyWorker class
class MyWorker : System.MulticastDelegate
{
public int Invoke(char c,int m);
public System.IAsyncResult BeginInvoke(char c,int m,System.AsyncCallback cb,
object asyncState);
public int EndInvoke(System.IAsyncResult result);
}
9.10. TIMERS 133
The BeginInvoke and EndInvoke methods can be used for asynchronous invocation
of a method pointed by MyWorker delegate. In the program below the method
DoWork (it displays character c m number of times) is invoked asynchronously with
character ’+’, in the next statement this method is directly invoked with character
’*’. Both ’+’ and ’*’ are displayed (1000 times each) on the Console simultaneously.
As an asynchronous call executes within its own background thread, we have
used Console.Read() to pause the main thread.
// Asynchronous.cs
using System;
delegate int MyWorker(char c,int m);
class AsncTest
{
static MyWorker worker;
static int DoWork(char c,int m)
{
int t = Environment.TickCount; // returns number of milliseconds
// elapsed since the system started
for(int i=1;i<=m;i++) Console.Write(c);
return (Environment.TickCount - t);
}
public static void Main()
{
Console.WriteLine("Start");
worker = new MyWorker(DoWork);
worker.BeginInvoke(’+’,1000,null,null); // asynchronous call
DoWork(’*’,1000); // synchronous call
Console.Read(); // pause until user presses a key
}
}
9.10 Timers
There are various different timers available in .NET, each of which basically calls
a delegate after a certain amount of time has passed. All the timers implement
IDispossible. so we have to make sure to dispose when we are not using them
anymore.
// Timers.cs
134 CHAPTER 9. PROCESSES AND THREADS
using System;
using System.Threading;
public class Timers
{
public static void Main()
{
Console.WriteLine("Started at {0:HH:mm:ss.fff}",DateTime.Now);
// Start in three seconds, then fire every second
using(Timer timer = new Timer(new TimerCallback(Tick),null,3000,1000))
{
// wait for 10 seconds
Thread.Sleep(10000);
// then go slow for another 10 seconds
timer.Change(0,2000);
Thread.Sleep(10000);
}
// the timerm will now have been disposed automatically due
// to the using statement so there won’t be any other threads running
} // end Main()
static void Tick(object state)
{
Console.WriteLine("Ticked at {0:HH:mm:ss.fff}",DateTime.Now);
}
}
9.11 Interrupt and Abort
There are two methods in the Thread class which are used for stopping threads -
Abort and Interrupt. Calling Thread.Abort aborts that thread as soon as possible.
Aborting a thread which is executing unmanaged code has no effect until the CLR
gets control again. Calling Thread.Interrupt is similar, but less drastic. This
causes a ThreadInterruptedException exception to be thrown the next time the
thread enters the WaitSleepJoin state, or immediately if the thread is already in
that state.
// Interruptthread.cs
using System;
using System.Threading;
class SleepingThread
{
public static void Run()
9.11. INTERRUPT AND ABORT 135
{
for(int i=1;i<6;i++)
Console.WriteLine("Welcome {0}",i);
try
{
Thread.Sleep(5000);
}
catch(ThreadInterruptedException e)
{
Console.WriteLine("Sleep Interrupted");
}
Console.WriteLine("Goodbye");
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
for(int i=1;i<16;i++)
Console.WriteLine("Hello {0}",i);
t.Interrupt();
}
}
The program threadtest1.cs uses the Abort method.
// threadtest1.cs
using System;
using System.Threading; // for Thread class
class ThreadTest1
{
public static void SayHello()
{
for(int i=1;;i++)
{
Console.WriteLine("Hello {0}",i);
}
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(SayHello));
136 CHAPTER 9. PROCESSES AND THREADS
t.Start();
for(int i=1;i<1001;i++)
{
Console.WriteLine("Bye {0}",i);
}
t.Abort();
}
}
programs on events
Events
Event handling is familiar to any developer who has programmed graphical user interfaces
(GUI). When a user interacts with a GUI control (e.g., clicking a button on
a form), one or more methods are executed in response to the above event. Events
can also be generated without user interactions. Event handlers are methods in an
object that are executed in response to some events occuring in the application. To
understand the event handling model of the .NET framework, we need to unterstand
the concept of delegate. A delegate in C# allows us to pass methods of one
class to objects to other classes that can call those methods. We can pass method
m in class A, wrapped in a delegate, to class B and class B will be able to call
method m. We can pass both static and instance methods.
An event handler in C# is a delegate with a special signature, given below
public delegate void MyEventHandler(object sender,MyEventArgs e)
The first parameter (sender) in the above declaration specifies the object that fired
the event. The second parameter (e) holds data that can be used in the event handler.
The class MyEventArgs is derived from the class EventArgs. EventArgs is the
base class of more specialized classes, like MouseEventArgs, ListChangedEventArgs,
etc.
Thus an event is a notification sent by a sender object to a listener object. The listener
registers an event handler method with the sender. When the event occurs the
sender invokes event handler methods of all its registered listerners. The event handler
is registered by adding +=. The -= operator removes the handler from the event.
As an example we set up two classes A and B to see how this event handling mechanism
works in .NET framework. The delegates require that we define methods with
the exact same signature as that of the delegate declaration. Class A will provide
event handlers (methods with the same signature as that of the event declaration).
It will create the delegate objects and hook up the event handler. Class A will then
pass the delegate objects to class B. When an event occurs in class B, it will execute
the event handler method of class B.
110
111
// MyHandler.cs
using System;
// Step 1. Create delegate object
public delegate void MyHandler1(object sender,MyEventArgs e);
public delegate void MyHandler2(object sender,MyEventArgs e);
// Step 2. Create event handler methods
class A
{
public const string m_id = "Class A";
public void OnHandler1(object sender,MyEventArgs e)
{
Console.WriteLine("I am in OnHandler1 and MyEventArgs is {0}",e.m_id);
}
public void OnHandler2(object sender,MyEventArgs e)
{
Console.WriteLine("I am in OnHandler2 and MyEventArgs is {0}",e.m_id);
}
// Step 3. Create delegates, plug in the handler and register
// with the object that will fire the events
public A(B b)
{
MyHandler1 d1 = new MyHandler1(OnHandler1);
MyHandler2 d2 = new MyHandler2(OnHandler2);
b.Event1 += d1;
b.Event2 += d2;
}
} // end class A
// Step 4. Call the encapsulated method through the delegate (fires events)
class B
{
public event MyHandler1 Event1;
public event MyHandler2 Event2;
public void FireEvent1(MyEventArgs e)
{
if(Event1 != null) { Event1(this,e); }
}
public void FireEvent2(MyEventArgs e)
{
if(Event2 != null) { Event2(this,e); }
112 CHAPTER 8. EVENTS
}
} // end class B
public class MyEventArgs
{
public string m_id;
} // end class MyEventArgs
class Driver
{
public static void Main()
{
B b = new B();
A a = new A(b);
MyEventArgs e1 = new MyEventArgs();
MyEventArgs e2 = new MyEventArgs();
e1.m_id = "Event args for event 1";
e2.m_id = "Event args for event 2";
b.FireEvent1(e1);
b.FireEvent2(e2);
} // end Main
}
The next program shows GUI event handling in C#. We create two buttons each of
them fires an event.
// evthand.cs
using System;
using System.Windows.Forms;
using System.Drawing;
class MyForm : Form
{
MyForm()
{
// button1 top left corner
Button button1 = new Button();
button1.Text = "Button";
button1.Click += new EventHandler(HandleClick1);
Controls.Add(button1);
Button button2 = new Button();
button2.Location = new Point(40,40);
button2.Size = new Size(60,40);
113
button2.Text = "New Button";
button2.Click += new EventHandler(HandleClick2);
Controls.Add(button2);
}
void HandleClick1(object sender,EventArgs e)
{
MessageBox.Show("The click event fire!");
}
void HandleClick2(object sender,EventArgs e)
{
Console.WriteLine("Console click fire!");
}
public static void Main()
{
Application.Run(new MyForm());
}
}
Event handling is familiar to any developer who has programmed graphical user interfaces
(GUI). When a user interacts with a GUI control (e.g., clicking a button on
a form), one or more methods are executed in response to the above event. Events
can also be generated without user interactions. Event handlers are methods in an
object that are executed in response to some events occuring in the application. To
understand the event handling model of the .NET framework, we need to unterstand
the concept of delegate. A delegate in C# allows us to pass methods of one
class to objects to other classes that can call those methods. We can pass method
m in class A, wrapped in a delegate, to class B and class B will be able to call
method m. We can pass both static and instance methods.
An event handler in C# is a delegate with a special signature, given below
public delegate void MyEventHandler(object sender,MyEventArgs e)
The first parameter (sender) in the above declaration specifies the object that fired
the event. The second parameter (e) holds data that can be used in the event handler.
The class MyEventArgs is derived from the class EventArgs. EventArgs is the
base class of more specialized classes, like MouseEventArgs, ListChangedEventArgs,
etc.
Thus an event is a notification sent by a sender object to a listener object. The listener
registers an event handler method with the sender. When the event occurs the
sender invokes event handler methods of all its registered listerners. The event handler
is registered by adding +=. The -= operator removes the handler from the event.
As an example we set up two classes A and B to see how this event handling mechanism
works in .NET framework. The delegates require that we define methods with
the exact same signature as that of the delegate declaration. Class A will provide
event handlers (methods with the same signature as that of the event declaration).
It will create the delegate objects and hook up the event handler. Class A will then
pass the delegate objects to class B. When an event occurs in class B, it will execute
the event handler method of class B.
110
111
// MyHandler.cs
using System;
// Step 1. Create delegate object
public delegate void MyHandler1(object sender,MyEventArgs e);
public delegate void MyHandler2(object sender,MyEventArgs e);
// Step 2. Create event handler methods
class A
{
public const string m_id = "Class A";
public void OnHandler1(object sender,MyEventArgs e)
{
Console.WriteLine("I am in OnHandler1 and MyEventArgs is {0}",e.m_id);
}
public void OnHandler2(object sender,MyEventArgs e)
{
Console.WriteLine("I am in OnHandler2 and MyEventArgs is {0}",e.m_id);
}
// Step 3. Create delegates, plug in the handler and register
// with the object that will fire the events
public A(B b)
{
MyHandler1 d1 = new MyHandler1(OnHandler1);
MyHandler2 d2 = new MyHandler2(OnHandler2);
b.Event1 += d1;
b.Event2 += d2;
}
} // end class A
// Step 4. Call the encapsulated method through the delegate (fires events)
class B
{
public event MyHandler1 Event1;
public event MyHandler2 Event2;
public void FireEvent1(MyEventArgs e)
{
if(Event1 != null) { Event1(this,e); }
}
public void FireEvent2(MyEventArgs e)
{
if(Event2 != null) { Event2(this,e); }
112 CHAPTER 8. EVENTS
}
} // end class B
public class MyEventArgs
{
public string m_id;
} // end class MyEventArgs
class Driver
{
public static void Main()
{
B b = new B();
A a = new A(b);
MyEventArgs e1 = new MyEventArgs();
MyEventArgs e2 = new MyEventArgs();
e1.m_id = "Event args for event 1";
e2.m_id = "Event args for event 2";
b.FireEvent1(e1);
b.FireEvent2(e2);
} // end Main
}
The next program shows GUI event handling in C#. We create two buttons each of
them fires an event.
// evthand.cs
using System;
using System.Windows.Forms;
using System.Drawing;
class MyForm : Form
{
MyForm()
{
// button1 top left corner
Button button1 = new Button();
button1.Text = "Button";
button1.Click += new EventHandler(HandleClick1);
Controls.Add(button1);
Button button2 = new Button();
button2.Location = new Point(40,40);
button2.Size = new Size(60,40);
113
button2.Text = "New Button";
button2.Click += new EventHandler(HandleClick2);
Controls.Add(button2);
}
void HandleClick1(object sender,EventArgs e)
{
MessageBox.Show("The click event fire!");
}
void HandleClick2(object sender,EventArgs e)
{
Console.WriteLine("Console click fire!");
}
public static void Main()
{
Application.Run(new MyForm());
}
}
programs on graphics
Graphics
7.1 Drawing Methods
The Windows.Forms namespace contains classes for creating Windows based applications.
In this namespace we find the Form class and many other controls that can
be added to forms to create user interfaces. The System.Drawing namespace provides
access to GDI + basic graphics functionality. The Graphics object provides
methods for drawing a variety of lines and shapes. Simple or complex shapes can
be rendered in solid or transparent colors, or using user-defined gradient or image
textures. Lines, open curves, and outline shapes are created using a Pen object. To
fill in an area, such as a rectangle or a closed curve, a Brush object is required. The
drawing methods are
DrawString(), DrawLine(), DrawRectangle(),
DrawEllipse(), DrawPie(), DrawPolygon(),
DrawArc()
and DrawImage(). Using DrawImage() we can display an image.
The method DrawString() takes five arguments
DrawString(string,Font,Brush,int X,int Y)
The first argument is a string, i.e. the text we want to display. The last two
arguments is the position where we put the string. The method DrawEllipse() is
given by
DrawEllipse(System.Drawing.Pen,float x,float y,float width,float height)
Every method in the Graphics class has to be accessed by creating an object of that
class. We can easily update the above program to render other graphical shapes like
Rectangle, Ellipse, etc. All we have to do is to apply the relevant methods appropriately.
96
7.1. DRAWING METHODS 97
Using the Pen class we can specify colour of the border and also the thickness. The
Pen class is applied for drawing shapes. The Brush is applied for filling shapes such
as SolidBrush and HatchStyleBrush.
The default Graphics unit is Pixel. By applying the PageUnit property, we can
change the unit of measurement to Inch and Millimeter.
// HelloGraphics.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class Hello : Form
{
public Hello()
{
this.Paint += new PaintEventHandler(f1_paint);
}
private void f1_paint(object sender,PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawString("Hello C#",new Font("Verdana",20),new SolidBrush(Color.Tomato),
40,40);
g.DrawRectangle(new Pen(Color.Pink,3),20,20,150,100);
}
public static void Main()
{
Application.Run(new Hello());
}
}
The method
DrawPolygon(System.Drawing.Pen,new Point[]{
new Point(x,y),new Point(x,y),
new Point(x,y),new Point(x,y),
new Point(x,y),new Point(x,y)});
draws a polygon for a given set of points. The following program shows how do
draw two triangles using this method.
98 CHAPTER 7. GRAPHICS
// MyPolygon.cs
using System;
using System.Drawing;
using System.Windows.Forms;
class Triangle : Form
{
public Triangle()
{
this.Paint += new PaintEventHandler(draw_triangle);
}
public void draw_triangle(Object sender,PaintEventArgs e)
{
Graphics g = e.Graphics;
Pen pen1 = new Pen(Color.Blue,2);
Pen pen2 = new Pen(Color.Green,2);
g.DrawPolygon(pen1,new Point[]{new Point(150,50),
new Point(50,150),new Point(250,150)});
g.DrawPolygon(pen2,new Point[]{new Point(50,155),
new Point(250,155),new Point(250,255)});
}
public static void Main()
{
Application.Run(new Triangle());
}
}
The following program shows how to use
FillRectangle(Brush,float x,float y,float width,float height);
FillEllipse(Brush,float x,float y, float width,float height);
FillPie(Brush,float x,float y,float width,float height,float angleX,float angleY);
// Fill.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class Fill : Form
{
public Fill()
7.2. COLOR CLASS 99
{
this.Paint += new PaintEventHandler(fillpaint);
}
private void fillpaint(object sender,PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(Color.Red),15,15,100,150);
g.FillEllipse(new SolidBrush(Color.Blue),50,50,150,120);
g.FillPie(new SolidBrush(Color.Yellow),200,200,40,40,0,90);
}
public static void Main()
{
Application.Run(new Fill());
}
}
7.2 Color Class
The Color class provides “constants” for common colors such as White, Black,
Blue, Red, Green, Pink. To create a Color structure from the specified 8-bit Color
(red,green,blue) we call for example
Color myColor = Color.FromArgb(0,255,125);
The alpha-value is implicit 255 (no transparency). To create a Color structure from
the four ARGB component (alpha,red,green,blue) we call for example
Color myColor = Color.FromArgb(51,255,0,0);
Alpha is also known as transparency where 255 is totally solid and 0 is totally
transparent.
// draw.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class Draw : Form
{
public Draw() { } // default constructor
protected override void OnPaint(PaintEventArgs e)
100 CHAPTER 7. GRAPHICS
{
FontFamily fontFamily = new FontFamily("Times New Roman");
Font font = new Font(fontFamily,24,FontStyle.Bold,GraphicsUnit.Pixel);
PointF pointF = new PointF(30,10);
SolidBrush solidbrush =
new SolidBrush(Color.FromArgb(51,255,0,0));
e.Graphics.DrawString ("Hello",font,solidbrush,pointF);
Pen myPen = new Pen(Color.Red);
myPen.Width = 50;
e.Graphics.DrawEllipse(myPen,new Rectangle(33,45,40,50));
e.Graphics.DrawLine(myPen,1,1,45,65);
e.Graphics.DrawBezier(myPen,15,15,30,30,45,30,87,20);
}
public static void Main()
{
Application.Run(new Draw());
}
}
In C# the user can choose a color by applying the ColorDialog class appropriatly.
First we have to create an object of ColorDialog class
ColorDialog cd = new ColorDialog();
An example is given in the followsing program
// ColorD.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class ColorD : Form
{
Button b = new Button();
TextBox tb = new TextBox();
ColorDialog clg = new ColorDialog();
public ColorD()
{
b.Click += new EventHandler(b_click);
b.Text = "OK";
tb.Location = new Point(50,50);
this.Controls.Add(b);
7.3. BUTTON AND EVENTHANDLER 101
this.Controls.Add(tb);
}
public void b_click(object sender,EventArgs e)
{
clg.ShowDialog();
tb.BackColor = clg.Color;
}
public static void Main(string[] args)
{
Application.Run(new ColorD());
}
}
7.3 Button and EventHandler
The Button class defines a Click event of type EventHandler. Inside the Button
class, the Click member is exactly like a private field of type EventHandler. However,
outside the Button class, the Click member can only be used on the left-hand
side of the += and -= operators. The operator += adds a handler for the event, and
the -= operator removes a handler for the event.
// Winhello.cs
using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
class WinHello : Form
{
private Button bnclick;
public WinHello()
{
Text = "Hello World";
Size = new Size(400,400);
bnclick = new Button();
bnclick.Text = "Click.Me";
bnclick.Size = new Size(60,24);
bnclick.Location = new Point(20,60);
bnclick.Click += new EventHandler(bnclick_Click);
Controls.Add(bnclick);
Closing += new CancelEventHandler(WinHello_Closing);
102 CHAPTER 7. GRAPHICS
}
private void bnclick_Click(object sender,EventArgs ev)
{
MessageBox.Show("Hello Egoli!!!!","Button Clicked",
MessageBoxButtons.OK,MessageBoxIcon.Information);
}
private void WinHello_Closing(object sender,CancelEventArgs ev)
{
if(MessageBox.Show("Are you sure?","Confirm exit",
MessageBoxButtons.YesNo,MessageBoxIcon.Question)
== DialogResult.No) ev.Cancel = true;
}
// Initialize the main thread
// using Single Threaded Apartment (STA) model
public static void Main()
{
Application.Run(new WinHello());
}
}
We can create a Font selection dialog box using the FontDialog class. The following
program gives an example
// Fonts.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class Fonts : Form
{
Button b = new Button();
TextBox tb = new TextBox();
FontDialog flg = new FontDialog();
public Fonts()
{
b.Click += new EventHandler(b_click);
b.Text = "OK";
tb.Location = new Point(50,50);
this.Controls.Add(b);
this.Controls.Add(tb);
7.4. DISPLAYING IMAGES 103
}
public void b_click(object sender,EventArgs e)
{
flg.ShowDialog();
tb.Font = flg.Font;
}
public static void Main(string[] args)
{
Application.Run(new Fonts());
}
}
7.4 Displaying Images
Next we provide a program that displays images using the method DrawImage. The
file formats could be bmp, gif or jpeg.
// mydrawim.cs
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class Texturedbru : Form
{
public Texturedbru()
{
this.Text = "Using Texture Brushes";
this.Paint += new PaintEventHandler(Text_bru);
}
public void Text_bru(object sender,PaintEventArgs e)
{
Graphics g = e.Graphics;
Image bgimage = new Bitmap("forest.bmp");
g.DrawImage(bgimage,20,20,1000,600);
}
public static void Main()
{
Application.Run(new Texturedbru());
104 CHAPTER 7. GRAPHICS
}
}
Another option to display the picture is
// textbru.cs
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class Texturedbru : Form
{
Brush bgbrush;
public Texturedbru()
{
this.Text = "Using Texture Brushes";
Image bgimage = new Bitmap("forest.bmp");
bgbrush = new TextureBrush(bgimage);
this.Paint += new PaintEventHandler(Text_bru);
}
public void Text_bru(object sender,PaintEventArgs e )
{
Graphics g = e.Graphics;
g.FillEllipse(bgbrush,50,50,500,300);
g.FillEllipse(bgbrush,150,150,450,300);
g.FillRectangle(bgbrush,350,450,100,130);
}
public static void Main()
{
Application.Run(new Texturedbru());
}
}
7.5 Overriding OnPaint
The next program shows how to override OnPaint(). We create a Button and
clicking on it switches the color of the circle from red to blue and vice versa.
// MyOnPaint.cs
7.5. OVERRIDING ONPAINT 105
using System;
using System.Drawing;
using System.Windows.Forms;
class Draw : Form
{
private SolidBrush b = new SolidBrush(Color.Red);
public Draw()
{
Button button1 = new Button();
button1.Text = "Click Me";
button1.Location = new Point(210,220);
button1.Click += new EventHandler(HandleClick);
Controls.Add(button1);
}
protected override void OnPaint(PaintEventArgs e)
{
int diameter = 200;
int x = 50;
int y = 30;
if(b.Color == Color.Blue)
{
b.Color = Color.Red;
}
else
{
b.Color = Color.Blue;
}
e.Graphics.FillEllipse(b,x,y,diameter,diameter);
} // end OnPaint
void HandleClick(object sender,EventArgs e)
{
this.Refresh();
}
public static void Main()
{
Application.Run(new Draw());
}
}
106 CHAPTER 7. GRAPHICS
Another example we consider a rotation line. We call the Invalidate() method to
force a repaint at the end of the frame.
// Rotate.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System;
class TimerVector : Form
{
float rot;
public static void Main()
{
Application.Run(new TimerVector());
}
private void TimerEvent(Object myObject,EventArgs myEventArgs)
{
Invalidate();
}
protected override void OnPaint(PaintEventArgs pea)
{
Graphics g = pea.Graphics;
GraphicsState gs = g.Save();
Pen myPen = new Pen(Color.Black,3);
float x1 = ClientSize.Width/4;
float y1 = ClientSize.Height/4;
float x2 = (ClientSize.Width*3)/4;
float y2 = (ClientSize.Height*3)/4;
float centerx = ClientSize.Width/2;
float centery = ClientSize.Height/2;
g.TranslateTransform(-centerx,-centery);
g.RotateTransform(rot,MatrixOrder.Append);
g.TranslateTransform(centerx,centery,MatrixOrder.Append);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawLine(myPen,x1,y1,x2,y2);
g.Restore(gs);
rot = (rot + 1)%360;
}
public TimerVector()
{
7.5. OVERRIDING ONPAINT 107
rot = 0.0f;
Timer t = new Timer();
t.Interval = 100;
t.Tick += new EventHandler(TimerEvent);
t.Start();
}
}
Another example is
// Flicker.cs
using System;
using System.Drawing;
using System.Windows.Forms;
class Flicker : Form
{
private Timer t = new Timer();
private Size playerSize = new Size(50,50);
private Point playerPosition = new Point(0,0);
public Flicker()
{
ClientSize = new Size(500,500);
SetStyle(ControlStyles.DoubleBuffer,true);
SetStyle(ControlStyles.UserPaint,true);
SetStyle(ControlStyles.AllPaintingInWmPaint,true);
t.Interval = 40;
t.Tick += new EventHandler(TimerOnTick);
t.Enabled = true;
this.KeyDown += new KeyEventHandler(OnKeyPress);
}
private void TimerOnTick(object sender,EventArgs e)
{
this.Refresh();
this.Text = DateTime.Now.ToString();
this.Text += " " + this.PlayerPosition.ToString();
}
private Point PlayerPosition
{
get { return this.playerPosition; }
108 CHAPTER 7. GRAPHICS
set
{
if(value.X < 0)
{
this.playerPosition.X = this.ClientSize.Width-this.playerSize.Width;
}
else if(value.X+this.playerSize.Width > this.ClientSize.Width)
{
this.playerPosition.X = 0;
}
else
{
this.playerPosition.X = value.X;
}
if(value.Y < 0)
{
this.playerPosition.Y = this.ClientSize.Height-this.playerSize.Height;
}
else if(value.Y+this.playerSize.Height > this.ClientSize.Height)
{
this.playerPosition.Y = 0;
}
else
{
this.playerPosition.Y = value.Y;
}
}
}
private void OnKeyPress(object sender,KeyEventArgs e)
{
if(e.KeyValue == 37)
{
this.PlayerPosition =
new Point(this.PlayerPosition.X-this.playerSize.Width,this.PlayerPosition.Y);
}
if(e.KeyValue == 38)
{
this.PlayerPosition =
new Point(this.PlayerPosition.X,this.PlayerPosition.Y-this.playerSize.Width);
}
if(e.KeyValue == 39)
{
this.PlayerPosition =
7.5. OVERRIDING ONPAINT 109
new Point(this.PlayerPosition.X+this.playerSize.Height,this.PlayerPosition.Y);
}
if(e.KeyValue == 40)
{
this.PlayerPosition =
new Point(this.PlayerPosition.X,this.PlayerPosition.Y+this.playerSize.Height);
}
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(new SolidBrush(Color.Red),this.PlayerPosition.X,
this.playerPosition.Y,this.playerSize.Width,
this.playerSize.Height);
}
public static void Main()
{
Application.Run(new Flicker());
}
}
7.1 Drawing Methods
The Windows.Forms namespace contains classes for creating Windows based applications.
In this namespace we find the Form class and many other controls that can
be added to forms to create user interfaces. The System.Drawing namespace provides
access to GDI + basic graphics functionality. The Graphics object provides
methods for drawing a variety of lines and shapes. Simple or complex shapes can
be rendered in solid or transparent colors, or using user-defined gradient or image
textures. Lines, open curves, and outline shapes are created using a Pen object. To
fill in an area, such as a rectangle or a closed curve, a Brush object is required. The
drawing methods are
DrawString(), DrawLine(), DrawRectangle(),
DrawEllipse(), DrawPie(), DrawPolygon(),
DrawArc()
and DrawImage(). Using DrawImage() we can display an image.
The method DrawString() takes five arguments
DrawString(string,Font,Brush,int X,int Y)
The first argument is a string, i.e. the text we want to display. The last two
arguments is the position where we put the string. The method DrawEllipse() is
given by
DrawEllipse(System.Drawing.Pen,float x,float y,float width,float height)
Every method in the Graphics class has to be accessed by creating an object of that
class. We can easily update the above program to render other graphical shapes like
Rectangle, Ellipse, etc. All we have to do is to apply the relevant methods appropriately.
96
7.1. DRAWING METHODS 97
Using the Pen class we can specify colour of the border and also the thickness. The
Pen class is applied for drawing shapes. The Brush is applied for filling shapes such
as SolidBrush and HatchStyleBrush.
The default Graphics unit is Pixel. By applying the PageUnit property, we can
change the unit of measurement to Inch and Millimeter.
// HelloGraphics.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class Hello : Form
{
public Hello()
{
this.Paint += new PaintEventHandler(f1_paint);
}
private void f1_paint(object sender,PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawString("Hello C#",new Font("Verdana",20),new SolidBrush(Color.Tomato),
40,40);
g.DrawRectangle(new Pen(Color.Pink,3),20,20,150,100);
}
public static void Main()
{
Application.Run(new Hello());
}
}
The method
DrawPolygon(System.Drawing.Pen,new Point[]{
new Point(x,y),new Point(x,y),
new Point(x,y),new Point(x,y),
new Point(x,y),new Point(x,y)});
draws a polygon for a given set of points. The following program shows how do
draw two triangles using this method.
98 CHAPTER 7. GRAPHICS
// MyPolygon.cs
using System;
using System.Drawing;
using System.Windows.Forms;
class Triangle : Form
{
public Triangle()
{
this.Paint += new PaintEventHandler(draw_triangle);
}
public void draw_triangle(Object sender,PaintEventArgs e)
{
Graphics g = e.Graphics;
Pen pen1 = new Pen(Color.Blue,2);
Pen pen2 = new Pen(Color.Green,2);
g.DrawPolygon(pen1,new Point[]{new Point(150,50),
new Point(50,150),new Point(250,150)});
g.DrawPolygon(pen2,new Point[]{new Point(50,155),
new Point(250,155),new Point(250,255)});
}
public static void Main()
{
Application.Run(new Triangle());
}
}
The following program shows how to use
FillRectangle(Brush,float x,float y,float width,float height);
FillEllipse(Brush,float x,float y, float width,float height);
FillPie(Brush,float x,float y,float width,float height,float angleX,float angleY);
// Fill.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class Fill : Form
{
public Fill()
7.2. COLOR CLASS 99
{
this.Paint += new PaintEventHandler(fillpaint);
}
private void fillpaint(object sender,PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(Color.Red),15,15,100,150);
g.FillEllipse(new SolidBrush(Color.Blue),50,50,150,120);
g.FillPie(new SolidBrush(Color.Yellow),200,200,40,40,0,90);
}
public static void Main()
{
Application.Run(new Fill());
}
}
7.2 Color Class
The Color class provides “constants” for common colors such as White, Black,
Blue, Red, Green, Pink. To create a Color structure from the specified 8-bit Color
(red,green,blue) we call for example
Color myColor = Color.FromArgb(0,255,125);
The alpha-value is implicit 255 (no transparency). To create a Color structure from
the four ARGB component (alpha,red,green,blue) we call for example
Color myColor = Color.FromArgb(51,255,0,0);
Alpha is also known as transparency where 255 is totally solid and 0 is totally
transparent.
// draw.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class Draw : Form
{
public Draw() { } // default constructor
protected override void OnPaint(PaintEventArgs e)
100 CHAPTER 7. GRAPHICS
{
FontFamily fontFamily = new FontFamily("Times New Roman");
Font font = new Font(fontFamily,24,FontStyle.Bold,GraphicsUnit.Pixel);
PointF pointF = new PointF(30,10);
SolidBrush solidbrush =
new SolidBrush(Color.FromArgb(51,255,0,0));
e.Graphics.DrawString ("Hello",font,solidbrush,pointF);
Pen myPen = new Pen(Color.Red);
myPen.Width = 50;
e.Graphics.DrawEllipse(myPen,new Rectangle(33,45,40,50));
e.Graphics.DrawLine(myPen,1,1,45,65);
e.Graphics.DrawBezier(myPen,15,15,30,30,45,30,87,20);
}
public static void Main()
{
Application.Run(new Draw());
}
}
In C# the user can choose a color by applying the ColorDialog class appropriatly.
First we have to create an object of ColorDialog class
ColorDialog cd = new ColorDialog();
An example is given in the followsing program
// ColorD.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class ColorD : Form
{
Button b = new Button();
TextBox tb = new TextBox();
ColorDialog clg = new ColorDialog();
public ColorD()
{
b.Click += new EventHandler(b_click);
b.Text = "OK";
tb.Location = new Point(50,50);
this.Controls.Add(b);
7.3. BUTTON AND EVENTHANDLER 101
this.Controls.Add(tb);
}
public void b_click(object sender,EventArgs e)
{
clg.ShowDialog();
tb.BackColor = clg.Color;
}
public static void Main(string[] args)
{
Application.Run(new ColorD());
}
}
7.3 Button and EventHandler
The Button class defines a Click event of type EventHandler. Inside the Button
class, the Click member is exactly like a private field of type EventHandler. However,
outside the Button class, the Click member can only be used on the left-hand
side of the += and -= operators. The operator += adds a handler for the event, and
the -= operator removes a handler for the event.
// Winhello.cs
using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
class WinHello : Form
{
private Button bnclick;
public WinHello()
{
Text = "Hello World";
Size = new Size(400,400);
bnclick = new Button();
bnclick.Text = "Click.Me";
bnclick.Size = new Size(60,24);
bnclick.Location = new Point(20,60);
bnclick.Click += new EventHandler(bnclick_Click);
Controls.Add(bnclick);
Closing += new CancelEventHandler(WinHello_Closing);
102 CHAPTER 7. GRAPHICS
}
private void bnclick_Click(object sender,EventArgs ev)
{
MessageBox.Show("Hello Egoli!!!!","Button Clicked",
MessageBoxButtons.OK,MessageBoxIcon.Information);
}
private void WinHello_Closing(object sender,CancelEventArgs ev)
{
if(MessageBox.Show("Are you sure?","Confirm exit",
MessageBoxButtons.YesNo,MessageBoxIcon.Question)
== DialogResult.No) ev.Cancel = true;
}
// Initialize the main thread
// using Single Threaded Apartment (STA) model
public static void Main()
{
Application.Run(new WinHello());
}
}
We can create a Font selection dialog box using the FontDialog class. The following
program gives an example
// Fonts.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class Fonts : Form
{
Button b = new Button();
TextBox tb = new TextBox();
FontDialog flg = new FontDialog();
public Fonts()
{
b.Click += new EventHandler(b_click);
b.Text = "OK";
tb.Location = new Point(50,50);
this.Controls.Add(b);
this.Controls.Add(tb);
7.4. DISPLAYING IMAGES 103
}
public void b_click(object sender,EventArgs e)
{
flg.ShowDialog();
tb.Font = flg.Font;
}
public static void Main(string[] args)
{
Application.Run(new Fonts());
}
}
7.4 Displaying Images
Next we provide a program that displays images using the method DrawImage. The
file formats could be bmp, gif or jpeg.
// mydrawim.cs
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class Texturedbru : Form
{
public Texturedbru()
{
this.Text = "Using Texture Brushes";
this.Paint += new PaintEventHandler(Text_bru);
}
public void Text_bru(object sender,PaintEventArgs e)
{
Graphics g = e.Graphics;
Image bgimage = new Bitmap("forest.bmp");
g.DrawImage(bgimage,20,20,1000,600);
}
public static void Main()
{
Application.Run(new Texturedbru());
104 CHAPTER 7. GRAPHICS
}
}
Another option to display the picture is
// textbru.cs
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class Texturedbru : Form
{
Brush bgbrush;
public Texturedbru()
{
this.Text = "Using Texture Brushes";
Image bgimage = new Bitmap("forest.bmp");
bgbrush = new TextureBrush(bgimage);
this.Paint += new PaintEventHandler(Text_bru);
}
public void Text_bru(object sender,PaintEventArgs e )
{
Graphics g = e.Graphics;
g.FillEllipse(bgbrush,50,50,500,300);
g.FillEllipse(bgbrush,150,150,450,300);
g.FillRectangle(bgbrush,350,450,100,130);
}
public static void Main()
{
Application.Run(new Texturedbru());
}
}
7.5 Overriding OnPaint
The next program shows how to override OnPaint(). We create a Button and
clicking on it switches the color of the circle from red to blue and vice versa.
// MyOnPaint.cs
7.5. OVERRIDING ONPAINT 105
using System;
using System.Drawing;
using System.Windows.Forms;
class Draw : Form
{
private SolidBrush b = new SolidBrush(Color.Red);
public Draw()
{
Button button1 = new Button();
button1.Text = "Click Me";
button1.Location = new Point(210,220);
button1.Click += new EventHandler(HandleClick);
Controls.Add(button1);
}
protected override void OnPaint(PaintEventArgs e)
{
int diameter = 200;
int x = 50;
int y = 30;
if(b.Color == Color.Blue)
{
b.Color = Color.Red;
}
else
{
b.Color = Color.Blue;
}
e.Graphics.FillEllipse(b,x,y,diameter,diameter);
} // end OnPaint
void HandleClick(object sender,EventArgs e)
{
this.Refresh();
}
public static void Main()
{
Application.Run(new Draw());
}
}
106 CHAPTER 7. GRAPHICS
Another example we consider a rotation line. We call the Invalidate() method to
force a repaint at the end of the frame.
// Rotate.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System;
class TimerVector : Form
{
float rot;
public static void Main()
{
Application.Run(new TimerVector());
}
private void TimerEvent(Object myObject,EventArgs myEventArgs)
{
Invalidate();
}
protected override void OnPaint(PaintEventArgs pea)
{
Graphics g = pea.Graphics;
GraphicsState gs = g.Save();
Pen myPen = new Pen(Color.Black,3);
float x1 = ClientSize.Width/4;
float y1 = ClientSize.Height/4;
float x2 = (ClientSize.Width*3)/4;
float y2 = (ClientSize.Height*3)/4;
float centerx = ClientSize.Width/2;
float centery = ClientSize.Height/2;
g.TranslateTransform(-centerx,-centery);
g.RotateTransform(rot,MatrixOrder.Append);
g.TranslateTransform(centerx,centery,MatrixOrder.Append);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawLine(myPen,x1,y1,x2,y2);
g.Restore(gs);
rot = (rot + 1)%360;
}
public TimerVector()
{
7.5. OVERRIDING ONPAINT 107
rot = 0.0f;
Timer t = new Timer();
t.Interval = 100;
t.Tick += new EventHandler(TimerEvent);
t.Start();
}
}
Another example is
// Flicker.cs
using System;
using System.Drawing;
using System.Windows.Forms;
class Flicker : Form
{
private Timer t = new Timer();
private Size playerSize = new Size(50,50);
private Point playerPosition = new Point(0,0);
public Flicker()
{
ClientSize = new Size(500,500);
SetStyle(ControlStyles.DoubleBuffer,true);
SetStyle(ControlStyles.UserPaint,true);
SetStyle(ControlStyles.AllPaintingInWmPaint,true);
t.Interval = 40;
t.Tick += new EventHandler(TimerOnTick);
t.Enabled = true;
this.KeyDown += new KeyEventHandler(OnKeyPress);
}
private void TimerOnTick(object sender,EventArgs e)
{
this.Refresh();
this.Text = DateTime.Now.ToString();
this.Text += " " + this.PlayerPosition.ToString();
}
private Point PlayerPosition
{
get { return this.playerPosition; }
108 CHAPTER 7. GRAPHICS
set
{
if(value.X < 0)
{
this.playerPosition.X = this.ClientSize.Width-this.playerSize.Width;
}
else if(value.X+this.playerSize.Width > this.ClientSize.Width)
{
this.playerPosition.X = 0;
}
else
{
this.playerPosition.X = value.X;
}
if(value.Y < 0)
{
this.playerPosition.Y = this.ClientSize.Height-this.playerSize.Height;
}
else if(value.Y+this.playerSize.Height > this.ClientSize.Height)
{
this.playerPosition.Y = 0;
}
else
{
this.playerPosition.Y = value.Y;
}
}
}
private void OnKeyPress(object sender,KeyEventArgs e)
{
if(e.KeyValue == 37)
{
this.PlayerPosition =
new Point(this.PlayerPosition.X-this.playerSize.Width,this.PlayerPosition.Y);
}
if(e.KeyValue == 38)
{
this.PlayerPosition =
new Point(this.PlayerPosition.X,this.PlayerPosition.Y-this.playerSize.Width);
}
if(e.KeyValue == 39)
{
this.PlayerPosition =
7.5. OVERRIDING ONPAINT 109
new Point(this.PlayerPosition.X+this.playerSize.Height,this.PlayerPosition.Y);
}
if(e.KeyValue == 40)
{
this.PlayerPosition =
new Point(this.PlayerPosition.X,this.PlayerPosition.Y+this.playerSize.Height);
}
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(new SolidBrush(Color.Red),this.PlayerPosition.X,
this.playerPosition.Y,this.playerSize.Width,
this.playerSize.Height);
}
public static void Main()
{
Application.Run(new Flicker());
}
}
Subscribe to:
Posts (Atom)