using System. Threading;
...
public class Car
{
string name;
int rate;
Random r = new Random();
public Car(string name, int rate)
{
this. name = name;
this. rate = rate;
}
public override string ToString()
{
return (" " + this. name +
", скорость=" + this. rate);
}
public void Pusk()
{
for (int i = 0; i < 100; i++)
{
rate += r. Next(;
if (rate < 0) rate = 0;
Console. WriteLine(this);
}
}
}
Сначала запускаем пример без использования потоков. Создаем объект First и запускаем метод Pusk для него, затем то же самое производим с объектом Second. Все действия выполняются в единственном, основном потоке – сначала для первого объекта, потом для второго.
public class Road
{
static void Main(string[] args)
{
Car first = new Car("Камаз-бетономешалка", 100);
first. Pusk();
Car second = new Car("Ока-кабриолет", 50);
second. Pusk();
Console. ReadLine();
}
}

Теперь изменим класс Road: создадим два независимых потока. Обратите внимание, что поток представляет собой только оболочку поверх некоторого выполняемого метода:
public class Road
{
static void Main(string[] args)
{
Car first = new Car("Камаз-бетономешалка", 100);
Thread firstThread = new Thread(
new ThreadStart(first. Pusk));
firstThread.Start();
Car second = new Car("Ока-кабриолет", 50);
Thread secondThread = new Thread(
new ThreadStart(second. Pusk));
secondThread.Start();
}
}
В этом примере (поскольку компьютер быстрый), для каждого автомобиля в течение интервала времени, выделенного для данного потока, успевает выполниться сразу несколько итераций.

Теперь заставим потоки «засыпать» на полсекунды. Для этого изменим метод Pusk. Благодаря паузам информация об автомобиле поступает «в режиме реального времени»
public void Pusk()
{
for (int i = 0; i < 100; i++)
{
rate += r. Next(;
if (rate < 0) rate = 0;
Console. WriteLine(this);
Thread. Sleep(500);
}
}

Многопотоковый сервер
Часто серверное приложение вынуждено работать сразу с несколькими клиентами (пример – чат-сервер).
Тогда для каждого клиента следует создавать отдельный поток.
Удобно разработать специальный класс для этих целей (назовем его Handler).

Рассмотрим пример сервера-«эха», который только и умеет - получать сообщение и тут же отправлять его обратно. Следующий класс является составной частью серверного проекта:
class Handler
{
Socket clientSocket; // сокет для связи с клиентом
NetworkStream netStream; // сетевой поток
BinaryWriter binWriter; // поток для записи
BinaryReader binReader; // поток для чтения
public Handler(Socket clientSocket)
{
this. clientSocket = clientSocket;
}
// Это метод для приема сообщений от клиента. Именно на
// него мы и будем «навешивать» отдельный поток в классе
// сервера
public void Reader()
{
// создаем потоки для чтения-записи
netStream = new NetworkStream(clientSocket);
binWriter = new BinaryWriter(netStream);
binReader = new BinaryReader(netStream);
// цикл приема сообщений от клиента
while (true)
{
// здесь возникает пауза, пока клиент
// не пришлет очередное сообщение
string message = binReader.ReadString();
// полученное сообщение выводим на экран
Console. WriteLine("Received:" + message);
// и отправляем обратно в качестве «эха»
binWriter. Write("Received:" + message);
// при получении сообщения о выходе клиента
if (message. Equals("quit"))
{
// разрываем с ним соединение
Console. WriteLine("Client disconnected");
binWriter. Close();
netStream. Close();
break;
}
}
}
Что изменится на сервере
while (true) // Ожидание запроса приемника
{
Socket clientSocket = tcpListener. AcceptSocket();
if (clientSocket. Connected)
{
// создаем объект обработчика для данного клиента
Handler hand = new Handler(clientSocket);
// создаем поток – оболочку на метод Reader
Thread mythread = new Thread(
new ThreadStart(hand. Reader));
// и запускаем поток
mythread. Start();
}
}
Обратите внимание, что всё общение с клиентом теперь перенесено в класс Handler.
Что изменится на клиенте
// Цикл передачи-приема сообщений выглядит следующим образом:
// клиент отправляет сообщение и ждет ответа, затем процесс
// повторяется
string message;
while (true)
{
message = Console. ReadLine();
binWriter. Write(message);
Console. WriteLine(binReader. ReadString());
// если мы отправили сообщение о выходе,
// закрываем сокет и завершаем работу
if (message. Equals("quit"))
{
serverSocket. Close();
break;
}
}
В примерах не обработаны исключения. Обработайте их самостоятельно!
Задание для самостоятельной работы
Создайте серверное и клиентское приложения по примеру.
Подумайте, как можно рассылать сообщения всем клиентам (как это обычно и бывает в чате).
Потоки на клиентской стороне клиент-серверных приложений
Как мы уже видели, на серверной стороне возникает необходимость создавать множество потоков при обмена данными со множеством клиентов (отдельному клиенту - отдельный поток).
Это касается, в основном, приема сообщений от клиентов, так как мы не можем точно предугадать, в какой момент времени от клиента придет очередное сообщение.
Но та же самая проблема может возникать и на клиентской стороне! Происходит это в приложениях, где сервер может присылать клиенту сообщения в произвольные моменты времени, как, например, чат-сервер. Поскольку обычно мы тоже не можем точно предугадать, когда от сервера придет очередное сообщение, приходится организовывать специальный поток для приема сообщений. Иначе пауза ожидания сообщений «заморозит» все элементы формы.
Вариант решения проблемы может быть таким: создаем отдельный метод с бесконечным циклом, в котором читается очередное сообщение от сервера и на этот метод «навешиваем» поток.

// переменные объекта
TcpClient serverSocket; // сокет
Thread potok = null; // поток для приема сообщений с сервера
NetworkStream netStream; // сетевой поток
BinaryReader binReader; // поток чтения
BinaryWriter binWriter; // поток записи
...
// в обработчике кнопки connect
// после подключения к серверу организуем
// поток-оболочку для метода read:
potok = new Thread(new ThreadStart(this.read));
potok. Start();
. . .
// Метод для приема сообщений с сервера. Он позволяет серверу
// присылать сообщения в любой момент
void read()
{
string msg=null;
do
{
// прочитать ответ
try
{
msg = binReader. ReadString();
// записать сообщение в текстовую область
result. AppendText("Received: " + msg + "\n");
}
catch (IOException) { } // ну, вдруг ошибка какая-нибудь
} while ((msg. ToLower()).CompareTo("exit") != 0);
}

Проблемы с доступом между потоками
Строка
result. AppendText("Received: " + msg + "\n");
при выполнении вызовет исключение:
Exception System.InvalidOperationException was thrown in debuggee:
Cross-thread operation not valid: Control ‘result' accessed from a thread other than the thread it was created on.
В чем же проблема? Дело в том, что мы пытаемся выполнить перекрестную операцию между потоками: получить доступ к элементу управления result, который был создан в основном потоке.
Здесь нам на помощь придут делегаты. Вспомним, что делегат представляет собой нечто похожее на указатель на функцию в С++.
Для элемента управления result выполним следующие действия:
result. Invoke(new MethodInvoker(delegate {
result. AppendText("Received: " + msg + "\n"); }));
Теперь проблемы с перекрестным доступом между потоками решены.
Базы данных в.NET
Как вы уже знаете, для работы с базами данных в. Net есть много разнообразных возможностей. Коротко напомним одну из самых старых и универсальных технологий для. Net –OLE DB.
Управляемый провайдер OLE DB
Провайдер OLE DB реализуется при помощи типов, определенных в пространстве имен System. Data. OleDb.
Провайдер OleDb позволяет нам обращаться к данным, расположенным в любом хранилище, к которому можно подключиться по протоколу OLE DB. Таким образом, аналогично классическому ADO, вы можете использовать в управляемый провайдер OLE DB для доступа, например, к базам данных SQL Server, MS Access и Oracle.
Однако поскольку при этом типы из пространства имен System. Data. OleDb должны взаимодействовать с обычным (не. NET) кодом драйверов OLE DB, то при таком обращении будет производиться множество преобразований вызовов. NET в вызовы СОМ, что в некоторых ситуациях может привести к падению производительности.
Наиболее важные типы пространства имен System. Data. OleDb
Тип | Описание |
OleDbCommand | Представляет запрос SQL, производимый к источнику данных |
OleDbConnection | Представляет открытое соединение с источником данных |
OleDbDataAdapter | Представляет соединение с базой данных и набор команд, используемых для заполнения объекта DataSet, а также обновления исходной базы данных после внесения изменений в DataSet |
OleDbDataReader | Обеспечивает метод считывания потока данных из источника в одном направлении (вперед) |
OleDbErrorCollection | OleDbErrorCollection представляет набор ошибок и предупреждений возвращаемых источником данных |
OleDbError | Сами эти ошибки и предупреждения представлены объектами OleDbError. |
OleDbException | При возникновении ошибки может быть сгенерировано исключение, представленное объектом OleDbException |
OleDbParameter OleDbParameterCollection | Используются для передачи параметров процедуре, хранимой на источнике данных. Параметры представлены объектами OleDbParameter, весь набор — объектом OleDbParameterCollection |
Установление соединения при помощи типа OleDbConnection
// Первый шаг: устанавливаем соединение
OleDbConnection cn = new OleDbConnection();
cn. ConnectionString = "Provider=SQLOLEDB.1;" +
"Integrated Security=SSPI;" +
"Persist Security Info=False;" +
"Initial Catalog=Kontora;" +
"Data Source=HOME-1E51FF62F3;";
cn. Open();
// Выполняем различные операции
cn. Close();
Data Source — это имя компьютера, с которым мы устанавливаем соединение.
Initial Catalog — имя базы данных, к которой мы подключаемся (в нашем случае Kontora).
Provider — это имя провайдера OLE DB, который будет использован для обращения к источнику данных.
Наиболее часто используемые провайдеры OLE DB:
Microsoft. JET. OLEDB.4.0 | Этот провайдер OLE DB используется для подключения к базам данных JET 9, то есть Access) |
MSDAORA | Для подключения к базам данных Oracle |
SQLOLEDB | Для подключения к базам данных MS SQL Server |
Члены класса OleDbConnection
BeginTransaction() CommitTransacton() RollbackTransaction() | Используются для того, чтобы программным образом начать транзакцию, завершить ее или отменить |
Close() | Закрывает соединение с источником данных (наиболее рекомендуемый способ) |
ConnectionString | Позволяет настроить строку подключения при установлении соединения или получить ее содержание |
ConnectonTimeout | Позволяет получить или установить время тайм-аута при установке соединения |
Database | Позволяет получить или установить название текущей базы данных во время подключения |
DataSource | Позволяет получить или установить имя источника данных |
Ореn() | Открывает соединение с базой данных, используя текущие настройки свойств соединения |
Provider | Позволяет получить или установить имя провайдера |
State | Позволяет получить информацию о текущем состоянии соединения |
Построение команды SQL
Объектно-ориентированным представлением запроса на языке SQL в является класс OleDbCommand. Сам текст команды определяется через свойство OleDbCommand. CommandText.
// Второй шаг: создаем команду SQL
// Первый вариант выполнения SQL-запроса
string strSQL1 = "Select * from k_firm":
OleDbCommand myCommand = new OleDbCommand(strSQL1, cn);
// Второй вариант выполнения SQL-запроса
string strSQL2 = "Select * from k_firm";
OleDbCommand myCommand2 = new OleDbCommand():
myCommand2.Connection = cn;
myConmand2.CommandText = strSQL2;
Члены класса OleDbCommand
Cancel() | Прекращает выполнение команды |
CommandText | Позволяет получить или задать текст запроса на языке SQL (возможно, с особенностями диалекта, определяемыми типом источника данных), который будет передан источнику данных |
CommandTimeout | Позволяет получить время тайм-аута при выполнении команды. По умолчанию это время равно 30 секундам |
CommandType | Позволяет получить или установить значение, определяющее, как именно будет интерпретирован текст запроса, заданный через свойство. |
Connection | Позволяет получить ссылку на объект OleDbConnection, для которого используется данный объект OleDbCommand |
ExecuteReader() | Возвращает объект OleDbDataReader |
Parameters | Возвращает коллекцию параметров OleDbParameterCollection |
Prepare() | Готовит команду к выполнению (например, она будет откомпилирована) на источнике данных |
Работа с OleDbDataReader
Этот класс представляет однонаправленный (только вперед), доступный только для чтения поток данных, который за один раз возвращает одну строку в ответ на запрос SQL.
// Третий шаг: получаем объект OleDbDataReader при помощи
// метода ExecuteReader()
OleDbDataReader myDataReader;
myDataReader = myCommand. ExecuteReader();
// Четвертый шаг: проходим циклом по всем возвращаемым данным
while (myDataReader. Read())
{
Console. WriteLine("Фирма: " + myDataReader["firm_name"].ToString());
}
myDataReader. Close();
Подключение к базе данных Access
OleDbConnection cn = new OleDbConnection();
cn. ConnectionString = "Provider=Microsoft. JET. OLEDB.4.0.;" +
@"Data Source=D:\book. mdb;";
Примечание: При работе в Visual Studio с 64-битовыми операционными системами для подключения к этому провайдеру требуется в свойствах проекта установить 32-битовую платформу (x86).
Некоторые проблемы и способы их решения |
Cross-thread operation not valid: Control 'someControle' accessed from a thread other than the thread it was created on.
someControle. Invoke(new MethodInvoker ( delegate { /* сюда помещаем наше действие */ })); |
Литература
1. Язык программирования C# 2010 и платформа. NET 4.0. Москва, Издательский дом "Вильямс", 2е издание).
2. Эндрю Кровчик, Винод Кумар, Номан Лагари, Аджит Мунгале, Кристиан Нагел, Тим Паркер, Шриниваса Шивакумар, .Net. Сетевое программирование для профессионалов. Москва, Лори, 2005.
3. П. Торстейнсон, , Криптография и безопасность в технологии. NET. Москва, Бином, 2007.
|
Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 |


Проблема: предположим, мы разработали серверное приложение и клиентское приложение и хотим разработать специальный класс «Сообщение» для обмена информацией между ними. Возникает вопрос: куда, в какой проект помещать этот специальный класс?