public void SendToOneChannel(object stateInfo)
        {
            var descriptor = (ChannelDescriptor)stateInfo;

            _identityProvider.Identity = new Identity(descriptor.CustomerCode);

            var channelService = ObjectFactoryBase.Resolve <INotificationChannelService>();
            var config         = ConfigDictionary[descriptor.CustomerCode];

            try
            {
                var key   = GetKey(descriptor.ChannelName, descriptor.CustomerCode);
                var state = _lockers[key];

                if (Monitor.TryEnter(state))
                {
                    try
                    {
                        if (!state.BlockState.HasValue ||
                            state.BlockState.Value.AddSeconds(config.WaitIntervalAfterErrors) <= DateTime.Now)
                        {
                            if (state.BlockState.HasValue)
                            {
                                _logger.Info(
                                    "Temporary channel lock has been released for channel {channel}, customer code {customerCode}",
                                    descriptor.ChannelName, descriptor.CustomerCode
                                    );
                                state.BlockState =
                                    null; //снимаем блокировку, если прошел указанные интервал и пробуем отправить снова
                            }

                            var service = ObjectFactoryBase.Resolve <IMessageService>();
                            var res     = service.GetMessagesToSend(descriptor.ChannelName, config.PackageSize);

                            if (res.IsSucceeded)
                            {
                                var channel    = GetChannel(config, descriptor.ChannelName);
                                var semaphore  = new SemaphoreSlim(Math.Max(channel.DegreeOfParallelism, 1));
                                var localState = new ChannelState()
                                {
                                    ErrorsCount = 0
                                };
                                var factoryMap = res.Result.Select(m => m.Key).Distinct()
                                                 .ToDictionary(k => k, k => new TaskFactory(new OrderedTaskScheduler()));
                                var tasks = res.Result
                                            .Select(m => SendOneMessage(descriptor.CustomerCode, descriptor.InstanceId, config,
                                                                        channel, service, m, semaphore, factoryMap[m.Key], localState, channelService))
                                            .ToArray();

                                Task.WaitAll(tasks);

                                if (localState.ErrorsCount >= config.ErrorCountBeforeWait)
                                {
                                    state.BlockState = DateTime.Now;
                                    _logger.Info(
                                        "Temporary channel lock has been acquired for channel {channel}, customer code {customerCode}",
                                        channel.Name, descriptor.CustomerCode
                                        );
                                }
                            }
                            else
                            {
                                state.BlockState = DateTime.Now;
                                _logger.Info(
                                    "Queue for channel {channel}, customer code {customerCode} is unavailable, temporary lock will be acquired",
                                    descriptor.ChannelName, descriptor.CustomerCode
                                    );
                            }
                        }
                    }
                    finally
                    {
                        Monitor.Exit(state);
                    }
                }
                else
                {
                    _logger.Info(
                        "Queue for channel {channel}, {customerCode} is busy",
                        descriptor.ChannelName, descriptor.CustomerCode
                        );
                }
            }
            catch (Exception ex)
            {
                _logger.Error().Exception(ex)
                .Message(
                    "An error occured while processing messages from the queue for channel {channel}, customer code {customerCode}",
                    descriptor.ChannelName, descriptor.CustomerCode
                    )
                .Write();
            }
        }
        private async Task SendOneMessage(
            string customerCode,
            string instanceId,
            NotificationSenderConfig config,
            NotificationChannel channel,
            IMessageService service,
            Message message,
            SemaphoreSlim semaphore,
            TaskFactory factory,
            ChannelState state,
            INotificationChannelService channelService)
        {
            await factory.StartNew(() =>
            {
                lock (state)
                {
                    if (state.ErrorsCount >= config.ErrorCountBeforeWait)
                    {
                        return;
                    }
                }

                var timer = new Stopwatch();
                timer.Start();
                string url = GetUrl(customerCode, instanceId, channel, message);

                try
                {
                    semaphore.Wait();
                    _logger.Debug("Start processing message {messageId} ", message.Id);


                    var request           = (HttpWebRequest)WebRequest.Create(url);
                    request.Method        = message.Method.ToUpper();
                    request.Timeout       = 1000 * config.TimeOut;
                    var mediaType         = !string.IsNullOrEmpty(channel.MediaType) ? channel.MediaType : "text/xml";
                    request.ContentType   = $"{mediaType}; charset=utf-8";
                    byte[] data           = Encoding.UTF8.GetBytes(message.Xml);
                    request.ContentLength = data.Length;

                    using (var streamWriter = request.GetRequestStream())
                    {
                        streamWriter.Write(data, 0, data.Length);
                        streamWriter.Flush();
                    }

                    using (var httpResponse = (HttpWebResponse)request.GetResponse())
                    {
                        timer.Stop();
                        _logger.Info()
                        .Message(
                            "Message {message} for channel {channel} has been sent on url {url}",
                            message.Method, channel.Name, Uri.UnescapeDataString(url)
                            )
                        .Property("productId", message.Key)
                        .Property("statusCode", httpResponse.StatusCode)
                        .Property("timeTaken", timer.ElapsedMilliseconds)
                        .Property("messageId", message.Id)
                        .Property("customerCode", customerCode)
                        .Write();

                        channelService.UpdateNotificationChannel(customerCode, channel.Name, message.Key,
                                                                 message.Created, httpResponse.StatusCode.ToString());
                    }

                    ;

                    service.RemoveMessage(message.Id);
                }
                catch (WebException ex)
                {
                    timer.Stop();

                    lock (state)
                    {
                        state.ErrorsCount++;
                    }

                    var httpResponse = ex.Response as HttpWebResponse;

                    if (httpResponse != null)
                    {
                        _logger.Info()
                        .Message(
                            "Message {message} for channel {channel} has been sent on url {url}",
                            message.Method, channel.Name, Uri.UnescapeDataString(url)
                            )
                        .Property("productId", message.Key)
                        .Property("statusCode", httpResponse.StatusCode)
                        .Property("timeTaken", timer.ElapsedMilliseconds)
                        .Property("messageId", message.Id)
                        .Property("customerCode", customerCode)
                        .Write();

                        channelService.UpdateNotificationChannel(customerCode, channel.Name, message.Key,
                                                                 message.Created, httpResponse.StatusCode.ToString());
                    }
                    else
                    {
                        _logger.Info()
                        .Message(
                            "Message {message} for channel {channel} has not been sent on url {url}",
                            message.Method, channel.Name, Uri.UnescapeDataString(url)
                            )
                        .Property("productId", message.Key)
                        .Property("statusCode", ex.Status)
                        .Property("timeTaken", timer.ElapsedMilliseconds)
                        .Property("messageId", message.Id)
                        .Property("customerCode", customerCode)
                        .Write();

                        channelService.UpdateNotificationChannel(customerCode, channel.Name, message.Key,
                                                                 message.Created, ex.Status.ToString());
                    }

                    _logger.Error().Exception(ex)
                    .Message(
                        "Message {message} for channel {channel} has not been sent on url {url}",
                        message.Method, channel.Name, Uri.UnescapeDataString(url)
                        )
                    .Property("productId", message.Key)
                    .Property("timeTaken", timer.ElapsedMilliseconds)
                    .Property("messageId", message.Id)
                    .Property("customerCode", customerCode)
                    .Write();
                }
                finally
                {
                    semaphore.Release();
                }
            });
        }
        private void UpdateConfiguration(string customerCode)
        {
            _identityProvider.Identity = new Identity(customerCode);
            var configProvider = ObjectFactoryBase.Resolve <INotificationProvider>();

            try
            {
                string instanceId = _props.InstanceId;
                _logger.Info()
                .Message("start UpdateConfiguration for {customerCode}", customerCode)
                .Property("instanceId", instanceId)
                .Write();

                int delay  = 0;
                var items  = _senders.Zip(_lockers.Keys, (s, k) => new { Sender = s, Key = k });
                var config = ConfigDictionary.AddOrUpdate(customerCode, code => configProvider.GetConfiguration(),
                                                          (code, cfg) => configProvider.GetConfiguration());

                foreach (var channel in config.Channels.Where(c => c.DegreeOfParallelism > 0))
                {
                    var key = GetKey(channel.Name, customerCode);

                    if (_lockers.ContainsKey(key))
                    {
                        var sender = items.First(itm => itm.Key == key).Sender;
                        sender.Change(
                            new TimeSpan(0, 0, delay),
                            new TimeSpan(0, 0, config.CheckInterval)
                            );
                        _logger.Info(
                            "Update sender for {key} whith delay {delay} and interval {interval}",
                            key, delay, config.CheckInterval
                            );
                    }
                    else
                    {
                        var state = new ChannelState {
                            BlockState = null, ErrorsCount = 0
                        };
                        var descriptor = new ChannelDescriptor
                        {
                            ChannelName = channel.Name, CustomerCode = customerCode, InstanceId = instanceId
                        };
                        _lockers.Add(key, state);
                        _senders.Add(new Timer((SendToOneChannel), descriptor, new TimeSpan(0, 0, delay),
                                               new TimeSpan(0, 0, config.CheckInterval)));
                        _logger.Info(
                            "Add sender for {key} whith delay {delay} and interval {interval}",
                            key, delay, config.CheckInterval
                            );
                    }

                    delay++;
                }

                if (customerCode != SingleCustomerCoreProvider.Key)
                {
                    var autopublishKey = GetKey(AutopublishKey, customerCode);

                    if (_lockers.ContainsKey(autopublishKey))
                    {
                        var sender = items.First(itm => itm.Key == autopublishKey).Sender;

                        if (config.Autopublish)
                        {
                            sender.Change(
                                new TimeSpan(0, 0, delay),
                                new TimeSpan(0, 0, config.CheckInterval)
                                );
                            _logger.Info(
                                "Update autopublish for {key} whith delay {delay} and interval {interval}",
                                autopublishKey, delay, config.CheckInterval
                                );
                        }
                        else
                        {
                            sender.Change(
                                new TimeSpan(0, 0, 0, 0, -1),
                                new TimeSpan(0, 0, config.CheckInterval)
                                );
                            _logger.Info(
                                "Stop autopublish for {key} whith delay {delay} and interval {interval}",
                                autopublishKey, delay, config.CheckInterval
                                );
                        }
                    }
                    else if (config.Autopublish)
                    {
                        var state = new ChannelState {
                            BlockState = null, ErrorsCount = 0
                        };
                        _lockers.Add(autopublishKey, state);
                        _senders.Add(new Timer(
                                         (Autopublish), customerCode,
                                         new TimeSpan(0, 0, delay),
                                         new TimeSpan(0, 0, config.CheckInterval)
                                         ));
                        _logger.Info("Add autopublish for {key} whith delay {delay} and interval {interval}",
                                     autopublishKey, delay, config.CheckInterval);
                    }

                    delay++;

                    var itemsToStop = items
                                      .Where(itm =>
                                             itm.Key.StartsWith(GetKeyPrefix(customerCode)) &&
                                             itm.Key != autopublishKey &&
                                             !config.Channels.Any(c =>
                                                                  GetKey(c.Name, customerCode) == itm.Key && c.DegreeOfParallelism > 0));

                    foreach (var item in itemsToStop)
                    {
                        item.Sender.Change(
                            new TimeSpan(0, 0, 0, 0, -1),
                            new TimeSpan(0, 0, config.CheckInterval)
                            );
                        _logger.Info("Stop sender for {key} whith delay {delay}", item.Key, delay);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error().Exception(ex)
                .Message("can not UpdateConfiguration for {customerCode}", customerCode)
                .Write();
            }
            finally
            {
                _logger.Info("end UpdateConfiguration for {customerCode}", customerCode);
            }
        }