Страницы

Поиск по вопросам

понедельник, 9 декабря 2019 г.

Как грамотно завершить работу self hosted wcf службы?

#c_sharp #net #wcf #windows_service


Допустим есть wcf служба, которая хостится в win service.

Если пользователь решит вырубить сервис, то как мне грамотно завершить работу wcf
службы? 

Допустим, один юзер вызвал метод по перемещению файлов, другой юзер что-то делает
с базой.

Если без логики завершить работу Win Service, то это приведет к необратимым последствиям.
    


Ответы

Ответ 1



Можно попробовать при остановке Win Service сигналить в WCF Service с помощью CancellationToken о том, что Win Service собирается останавливаться. WCF Service внутри себя должен проверять состояние данного CancellationToken, и, если запрошена отмена, выдавать исключение для вновь запрашиваемых операций, а уже выполняющиеся, например, прерывать. Win Service должен дождаться завершения отмены активных операций выполняемых WCF Service. Для отслеживания этого в WCF Service можно, к примеру, использовать ManualResetEvent и счётчик активных операций. Ниже примерная реализация (с достаточным числом упрощений). Контракт: [ServiceContract] public interface IWCFService { [OperationContract] string Echo(string str); } WCF Service: public class WCFService : IWCFService { CancellationToken ctOperations; public WCFService() { //у WinService берём CancellationToken остановки операций ctOperations = MyService.GetOperationsCancellationToken(); } public string Echo(string str) { //не выполняем новые операции, если началась остановка if (ctOperations.IsCancellationRequested) throw new Exception("Service stopping."); try { //увеличиваем счётчик активных операций IncActiveCnt(); //имитация деятельности int i = 0; while (i++ < 10) { ctOperations.ThrowIfCancellationRequested(); Thread.Sleep(500); } return "Echo " + str; } catch (OperationCanceledException) { throw new Exception("Service stopping."); } catch (Exception) { throw; } finally { //уменьшаем счётчик активных операций DecActiveCnt(); } } static object lockObj = new object(); static int activeOperationCnt = 0; static ManualResetEvent evtNoActiveOperations = new ManualResetEvent(true); //свойство для проверки в WinService static public ManualResetEvent NoActiveOperations { get { return evtNoActiveOperations; } } private void DecActiveCnt() { lock (lockObj) { if (--activeOperationCnt == 0) //сигналим, если нет активных операций evtNoActiveOperations.Set(); } } private void IncActiveCnt() { lock (lockObj) { activeOperationCnt++; //сбрасываем сигнал, если есть активные операции evtNoActiveOperations.Reset(); } } } Win Service (в данном примере не внешнее приложение, а он сам себе является клиентом WCF Service): public class MyService : ServiceBase { static CancellationTokenSource ctsOperations = null; static ServiceHost svcHost = null; public MyService() { } protected override void OnStart(string[] args) { //токен для остановки операций ctsOperations = new CancellationTokenSource(); svcHost = new ServiceHost(typeof(WCFService)); svcHost.Open(); //запускаем клиента WCF Service ThreadPool.QueueUserWorkItem(UseWCFService); //ждём 13 сек. Thread.Sleep(13000); //теперь остановим Win Service Stop(); } protected override void OnStop() { //сигналим отмену операций ctsOperations.Cancel(); int msecExtend = 3000; RequestAdditionalTime(msecExtend); //ждём завершение отмены операций WCFService.NoActiveOperations.WaitOne(msecExtend); svcHost.Close(); ctsOperations.Dispose(); } public static CancellationToken GetOperationsCancellationToken() { return ctsOperations.Token; } private void UseWCFService(object state) { //имитируем клиента Uri tcpUri = new Uri(@"http://localhost:8733/WCFService/"); EndpointAddress address = new EndpointAddress(tcpUri); BasicHttpBinding binding = new BasicHttpBinding(); ChannelFactory f = new ChannelFactory(binding, address); IWCFService svc = f.CreateChannel(); int i = 0; while (!ctsOperations.IsCancellationRequested) try { string echo = svc.Echo((++i).ToString()); Console.WriteLine(echo); } catch { Console.WriteLine("Error"); } } }

Ответ 2



Немножко обобщу и дополню ответ @i-one. Для того чтобы корректно завершать сервис, нужно две вещи: уметь сообщать нужному коду о том, что пора закругляться дожидаться пока этот код завершит свое исполнение Сигнализировать о сообщении можно разными способами, самый разумный -- CancellationToken, который при старте передается всему необходимому коду. Дожидаться завершения исполнения тоже можно разными способами. В случае с потоками/синхронным исполнением -- @i-one привел рецепт. В случае если приложение полностью асинхронное (с использованием async/await), в винсервисе на запуске запоминается таск, а на стопе этот таск await'ится. Еще один момент, который стоит упомянуть: по умолчанию, Service Console Manager выделяет 30 секунд на то, чтобы сервис завершился (если это значение не было переопределено в реестре). Если сервис не уложился в отведенное время, то в консоли он помечается как остановленный, но процесс при этом продолжает работать, что не очень корректно. Если корректное завершение требует много времени, то можно запросить дополнительное время -- вплоть до 125 секунд в сумме: protected override void OnStop() { int timeout = 10000; while (!mainTask.Wait(timeout)) { RequestAdditionalTime(timeout); } } Однако в любом случае полезно иметь некий общий таймаут (например, 120 секунд) и завершать работу принудительно (если вдруг какой-то код завис).

Комментариев нет:

Отправить комментарий