Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка ПИ Программирование на С# _Хотов.docx
Скачиваний:
4
Добавлен:
01.07.2025
Размер:
2.22 Mб
Скачать
        1. Прерывание параллельной операции

Вполне вероятно, что нам может потребоваться прекратить операцию до ее завершения. В этом случае мы можем использовать метод WithCancellation(), которому в качестве параметра передается токен CancellationToken:

static void Main(string[] args)

{

CancellationTokenSource cts = new CancellationTokenSource();

new Task(() =>

{

Thread.Sleep(400);

cts.Cancel();

}).Start();

try

{

int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, };

var factorials = from n in numbers.AsParallel().WithCancellation(cts.Token)

select Factorial(n);

foreach (var n in factorials)

Console.WriteLine(n);

}

catch(OperationCanceledException ex)

{

Console.WriteLine("Операция была прервана");

}

catch (AggregateException ex)

{

if (ex.InnerExceptions != null)

{

foreach (Exception e in ex.InnerExceptions)

Console.WriteLine(e.Message);

}

}

finally

{

cts.Dispose();

}

Console.ReadLine();

}

static int Factorial(int x)

{

int result = 1;

for (int i = 1; i <= x; i++)

{

result *= i;

}

Console.WriteLine("Факториал числа {0} равен {1}", x, result);

Thread.Sleep(1000);

return result;

}

В параллельной запущенной задаче вызывается метод cts.Cancel(), что приводит к завершению операции и генерации исключения OperationCanceledException:

При этом также имеет смысл обрабатывать исключение AggregateException, так как если параллельно возникает еще одно исключение, то это исключение, а также OperationCanceledException помещаются внутрь одного объекта AggregateException.

    1. Службы Windows

      1. Создание службы для Windows

Одним из важнейших компонентов ОС Windows являются службы. Фактически это отдельные приложения, которые не имеют графического интерфейса и которые выполняют различные задачи в фоновом режиме. Службы могут быть запущены при старте операционной системы, так и в любой другой момент работы пользователя. Распространенным примером служб являются различные веб-серверы, которые в фоновом режиме прослушивают определенный порт на наличие подключений, и если подключения имеются, то взаимодействуют с ними. Это могут быть также различные вспомогательные сервисы обновлений для других установленных программ, которые обращаются к серверу, чтобы узнать, есть ли новая версия приложения. В общем то мы можем открыть панель служб и сами увидеть все установленные и запущенные службы:

Рассмотрим, как создавать свои службы в C#. В качестве реализуемой задачи выберем наблюдение за изменениями в определенной папке в файловой системе. Теперь создадим для ее выполнения службу.

Вначале создадим новый проект, который будет иметь тип Windows Service. Назовем проект FileWatcherService:

После этого Visual Studio генерирует проект, который имеет все необходимое. Хотя в принципе нам необязательно выбирать именно этот тип проекта, можно было бы создать проект библиотеки классов, и затем в нем определить все необходимые классы.

Итак, новый проект выглядит следующим образом:

Здесь также есть файл Program.cs и есть собственно узел службы Service1.cs.

Служба представляет обычное приложение, но она не запускаетс сама по себе. Все вызовы и обращения к ней проходят через менеджер управления службами (Service Control Manager или SCM). Когда служба запускается автоматически при старте системы или вручную, то SCM обращается к методу Main в классе Program:

static class Program

{

static void Main()

{

ServiceBase[] ServicesToRun;

ServicesToRun = new ServiceBase[]

{

new Service1()

};

ServiceBase.Run(ServicesToRun);

}

}

Метод Main по умолчанию определен таким образом, чтобы запускать сразу несколько служб, которые определены в массиве ServicesToRun. Однако по умолчанию проект содержит только одну службу Service1. Сам запуск производится с помощью метода Run: ServiceBase.Run(ServicesToRun).

Сама запускаемая служба представлена узлом Service1.cs. Однако на самом деле это не простой файл кода. Если мы откроем этот узел, то увидим в нем файл дизайнера службы Service1.Designer.cs и класс Service1.

Класс Service1 собственно представляет службу. По умолчанию он имеет следующий код:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Diagnostics;

using System.Linq;

using System.ServiceProcess;

using System.Text;

using System.Threading.Tasks;

namespace FileWatcherService

{

public partial class Service1 : ServiceBase

{

public Service1()

{

InitializeComponent();

}

protected override void OnStart(string[] args)

{

}

protected override void OnStop()

{

}

}

}

Класс службы должен наследоваться от базового класса ServiceBase. Этот класс определяет ряд методов, важнейшие из которых метод OnStart(), который запускает действия, выпоняемые службой, и метод OnStop(), останавливающий службу.

После того, как SCM вызовет метод Main и зарегистрирует слубу, происходит непосредственный ее вызов через запуск метода OnStart.

Когда в консоли служб или через командную строку мы посылаем команду на остановку службы, то SCM обращается к методу OnStop для ее остановки.

Кроме этих двух методов в классе службы можно переопределить еще несколько методов базового класса ServiceBase:

  • OnPause: вызывается при приостановке службы

  • OnContinue: вызывается при возобновлении работы службы после ее приостановки

  • OnShutdown: вызывается при завершении работы Windows

  • OnPowerEvent: вызывается при изменении режима электропитания

  • OnCustomCommand: вызывается при получении службой пользовательской команды от Менеджера Управления Службами (Service Control Manager / SCM)

В конструкторе класса Service1 вызывается метод InitializeComponent(), который определен в файле дизайнера Service1.Designer.cs:

namespace FileWatcherService

{

partial class Service1

{

private System.ComponentModel.IContainer components = null;

protected override void Dispose(bool disposing)

{

if (disposing && (components != null))

{

components.Dispose();

}

base.Dispose(disposing);

}

private void InitializeComponent()

{

components = new System.ComponentModel.Container();

this.ServiceName = "Service1";

}

}

}

Единственное, что надо в нем отметить, это установка названия службы (свойство ServiceName):

this.ServiceName = "Service1";

Это то название, которое будет отображаться в консоли служб после установки данной службы. Мы можем его изменить, а можем и оставить как есть.

Теперь изменим код службы следующим образом:

using System;

using System.ServiceProcess;

using System.IO;

using System.Threading;

namespace FileWatcherService

{

public partial class Service1 : ServiceBase

{

Logger logger;

public Service1()

{

InitializeComponent();

this.CanStop = true;

this.CanPauseAndContinue = true;

this.AutoLog = true;

}

protected override void OnStart(string[] args)

{

logger = new Logger();

Thread loggerThread = new Thread(new ThreadStart(logger.Start));

loggerThread.Start();

}

protected override void OnStop()

{

logger.Stop();

Thread.Sleep(1000);

}

}

class Logger

{

FileSystemWatcher watcher;

object obj = new object();

bool enabled = true;

public Logger()

{

watcher = new FileSystemWatcher("D:\\Temp");

watcher.Deleted += Watcher_Deleted;

watcher.Created += Watcher_Created;

watcher.Changed += Watcher_Changed;

watcher.Renamed += Watcher_Renamed;

}

public void Start()

{

watcher.EnableRaisingEvents = true;

while(enabled)

{

Thread.Sleep(1000);

}

}

public void Stop()

{

watcher.EnableRaisingEvents = false;

enabled = false;

}

// переименование файлов

private void Watcher_Renamed(object sender, RenamedEventArgs e)

{

string fileEvent = "переименован в " + e.FullPath;

string filePath = e.OldFullPath;

RecordEntry(fileEvent, filePath);

}

// изменение файлов

private void Watcher_Changed(object sender, FileSystemEventArgs e)

{

string fileEvent = "изменен";

string filePath = e.FullPath;

RecordEntry(fileEvent, filePath);

}

// создание файлов

private void Watcher_Created(object sender, FileSystemEventArgs e)

{

string fileEvent = "создан";

string filePath = e.FullPath;

RecordEntry(fileEvent, filePath);

}

// удаление файлов

private void Watcher_Deleted(object sender, FileSystemEventArgs e)

{

string fileEvent = "удален";

string filePath = e.FullPath;

RecordEntry(fileEvent, filePath);

}

private void RecordEntry(string fileEvent, string filePath)

{

lock (obj)

{

using (StreamWriter writer = new StreamWriter("D:\\templog.txt", true))

{

writer.WriteLine(String.Format("{0} файл {1} был {2}",

DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss"), filePath, fileEvent));

writer.Flush();

}

}

}

}

}

Ключевым классом, который инкапсулирует всю функциональность, является класс Logger. С помощью объекта FileSystemWatcher он будет вести мониторинг изменений в папке D://Temp. В методе Start() устанавливается, что мы будем отслеживать изменения через объект FileSystemWatcher. И вся работа будет идти, пока булевая переменная enabled равнаtrue. А метод Stop() позволит завершить работу класса.

События FileSystemWatcher позволяют отслеживать все изменения в наблюдаемой папке. При этом будет вестись запись изменений в файл templog.txt. Чтобы не было гонки ресурсов за файл templog.txt, в который вносятся записи об изменениях, процедура записи блокируется заглушкой lock(obj).

В итоге после создания, изменения, переименования и удаления файл лога будет содержать что-то наподобие:

30.07.2015 12:15:40 файл D:\Temp\Новый текстовый документ.txt был создан

30.07.2015 12:15:46 файл D:\Temp\Новый текстовый документ.txt был переименован в D:\Temp\hello.txt

30.07.2015 12:15:55 файл D:\Temp\hello.txt был изменен

30.07.2015 12:15:55 файл D:\Temp\hello.txt был изменен

30.07.2015 12:16:01 файл D:\Temp\hello.txt был удален

В самом классе службы Service1 в конструкторе устанавливается ряд опций:

this.CanStop = true; // службу можно остановить

this.CanPauseAndContinue = true; // службу можно приостановить и затем продолжить

this.AutoLog = true; // служба может вести запись в лог

В методе OnStart() для запуска объекта Logger вызывется новый поток:

protected override void OnStart(string[] args)

{

logger = new Logger();

Thread loggerThread = new Thread(new ThreadStart(logger.Start));

loggerThread.Start();

}

Новый поток нужен, так как текущий поток обрабатывает только команды SCM и должен возвращаться из метода OnStart как можно быстрее.

Когда от менеджера SCM поступает команда на остановку службы, срабатывает метод OnStop, который вызывает методlogger.Stop(). Дополнительная задержка позволит потоку логгера остановиться:

protected override void OnStop()

{

logger.Stop();

Thread.Sleep(1000);

}

Однако самого класса службы еще недостаточно. Нам необходимо еще создать устанощик службы.