Поиск

Защита кода с помощью класса Monitor

Класс System. Monitor позволяет упорядочить обращения к блокам кода с помощью блокировки и освобождения. Например, ваш метод, обновляющий БД, не может выполняться двумя и более потоками одновременно. Если выполняемая им работа требует особенно много времени и у вас есть несколько потоков, любой из которых может его вызывать, может возникнуть серьезная проблема. Здесь в дело вступает класс Monitor. Взгляните на пример синхронизации. Здесь у нас два потока, каждый из которых будет вызывать метод Database.SaveData: using System; using System.Threading;

class Database {
public void SaveData(string text)
{
Console.WriteLine("Database.SaveData - Started");
Console.WriteLine("Database.SaveData - Working");
for (int 1=0; i < 100; i++)
{
Console.Write(text);
}
Console.WriteLine("\nDatabase.SaveData - Ended"); } }
class ThreadMonitorlApp
{
public static Database db = new DatabaseQ;
public static void WorkerThreadMethodlQ
<
Console.WriteLine("Worker thread "1 - Started");
Console.WriteLine
("Worker thread <M -Calling Database.SaveData"); db.SaveDataC'x");
Console.WriteLine("Worker thread *1 - Returned from Output"); }
public static void WorkerThreadMethod2()
{
Console.WriteLine("Worker thread #2 - Started");
Console.WriteLine
("Worker thread #2 - Calling Database.SaveData"); db.SaveData("o");
Console.WriteLine("Worker thread *2 - Returned from Output"); >
public static void MainQ
{
ThreadStart workerl = new ThreadStart(WorkerThreadMethodl);
ThreadStart worker2 = new ThreadStart(WorkerThreadMethod2);
Console.WriteLineC'Main - Creating worker threads");
Thread t1 = new Thread(workerl); Thread t2 = new Thread(worker2);
t1.Start(); t2.Start(); } }

Скомпилировав и запустив это приложение, вы увидите, что полученная в результате выходная информация будет состоять из смеси символов "о" и "х". Это говорит о том, что исполнение метода Database.Save-Data одновременно запускается обоими потоками (выходную информацию я снова сократил).

Main - Creating worker threads
Worker thread #1 - Started Worker thread #2 - Started
Worker thread #1 - Calling Database.SaveOata
Worker thread #2 - Calling Database.SaveData
Database.SaveData - Started Database.SaveData - Started
Database.SaveData - Working Database.SaveData - Working
xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxox Database.
SaveData - Ended Database.SaveData - Ended
Worker thread #1 - Returned from Output
Worker thread #2 - Returned from Output

Очевидно, если методу Database.SaveData нужно завершить обновление нескольких таблиц, прежде чем он будет вызван другим потоком, у нас будет серьезная проблема.

Для включения в этот пример класса Monitor мы воспользуемся двумя его статическими методами. Первый — Enter — во время исполнения пытается получить блокировку монитора для объекта. Если у другого потока уже есть эта блокировка, метод блокируется до тех пор, пока блокировка не будет освобождена. Заметьте: здесь не выполняется неявная операция упаковки, поэтому для этого метода вы можете предоставлять только ссылочные типы. Затем вызывается метод Monitor.Exit, чтобы освободить блокировку. Вот пример, переписанный для упорядоченного обращения к методу Database.SaveData:

using System;
using System.Threading;
class Database {
public void SaveData(string text)
{
Monitor.Enter(this);
Console.WriteLine("Database.SaveData - Started");
Console.WriteLine("Database.SaveData - Working");
for (int i = 0; i < 100; i++)
{
Console.Write(text); }
Console.WriteLine("\nDatabase.SaveData - Ended");
Monitor.Exit(this); > }
class ThreadMonitor2App
{
public static Database db = new Database();
public static void WorkerThreadMethod1()
{
Console.WriteLine("Worker thread #1 - Started");
Console.WriteLine
("Worker thread #1 - Calling Database.SaveData"); db.SaveDataC'x");
Console.WriteLine("Worker thread t1 - Returned from Output");
}
public static void WorkerThreadMethod2() {
Console.WriteLine("Worker thread #2 - Started");
Console.WriteLine
("Worker thread #2 - Calling Database.SaveData"); db.SaveData("o");
Console.WriteLine("Worker thread #2 - Returned from Output");
}
public static void Main() {
ThreadStart worker! = new ThreadStart(WorkerThreadMethodl);
ThreadStart worker2 = new ThreadStart(WorkerThreadMethod2);
Console.WriteLine("Main - Creating worker threads");
Thread t1 = new Thread(workerl); Thread t2 = new Thread(worker2);
t1.Start(); t2.Start(); } }

В приведенной ниже выходной информации обратите внимание на то, что даже если второй поток вызывал метод Database.SaveData, метод Monitor.Enter блокировал его до тех пор, пока первый поток не освобождал удерживаемую им блокировку:

Main - Creating worker threads
Worker thread #1 - Started Worker thread #2 - Started
Worker thread #1 - Calling Database.SaveData
Worker thread #2 - Calling Database.SaveData
Database.SaveData - Started Database.SaveData
- Working xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Database.SaveData - Ended
Database.SaveData - Started
Worker thread #1 - Returned from Output
Database.SaveData - Working
ooooooooooooooooooooooooooooooooooooooo Database.SaveData - Ended
Worker thread #2 - Returned from Output