Wątek przeniesiony 2018-10-08 10:05 z C# i .NET przez somekind.

Windows Service i post request w timerze

0

Hej,
było już coś takiego jednak do dziś nie znalazłem rozwiązania w necie. Uprościłem przykład do takiej postaci. Generalnie z poziomu usługi windowsowej chce puszczać POSTa do bramki sms co 1 min.

public partial class Service1 : ServiceBase
    {
        public static readonly string API_URL = "https://api1.serwersms.pl/zdalnie/";
        public Dictionary<string, string> _content;
        public string _path = @"D:\log_service.txt";

        private readonly IMessageService _messageService;
        private readonly IMessageRepository _messageRepository;
        private static HttpClient client = new HttpClient();
        private Timer timer;

        public Service1(IMessageService messageService, IMessageRepository messageRepository)
        {
            InitializeComponent();
            _messageService = messageService;
            _messageRepository = messageRepository;
        }

        protected override void OnStart(string[] args)
        {
            timer = new Timer(TimeSpan.FromMinutes(1).TotalMilliseconds);
            timer.AutoReset = true;
            timer.Enabled = true;
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Start();
        }

        private async void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            var _message = _messageRepository.GetByStatus(status.inQueue);

            {
                foreach (Message m in _message)
                {
                    _content = Services.Utils.SmsApiConfiguration.GetConfig();
                    _content.Add("wiadomosc", m.MessageBody);
                    _content.Add("numer", m.Phone);
                    _content.Add("akcja", "wyslij_sms");

                    var httpContent = new FormUrlEncodedContent(_content);

                    try
                    {
                        var response = await client.PostAsync(API_URL, httpContent);

                        if (response.IsSuccessStatusCode)
                        {
                            var responseContent = await response.Content.ReadAsStringAsync();
                            File.AppendAllText(_path, Environment.NewLine + " " + responseContent);
                        }
                    }
                    catch (Exception ex)
                    {
                        File.AppendAllText(_path, Environment.NewLine + " " + ex);
                    }
                }
            }
        }

        protected override void OnStop()
        {
            timer.Stop();
            timer = null;
        }
    }

Zupełnie nie potrafię poprawić tego tak aby zadziałało poprawnie tzn.
W kolejce są dwie wiadomości do wysłania i nigdy, dla przykładu nie wyrzucam ich z kolejki. I teraz:

  1. Uruchamiam serwis.
  2. 1szy Timer Tick -> wszystko się wysyła
  3. 2gi Timer Tick -> próba PostAsync() ( 1sza wiadomość ) dostaję

System.Net.WebException: Żądanie zostało przerwane: Nie można utworzyć bezpiecznego kanału SSL/TLS.

druga wiadomość leci bez problemu

i tak w kółko. Gdy w kolejce mam 10 wiadomości, za 1szym razem wszystko leci, kolejne "tiknięcia" 1sza wiadomość przerwanie, pozostałe 9 bez problemu.
Robię to na Win 10 i szczerze pomału mam dość i zaczynam myśleć o zrobieniu tego jak zwykłą apkę konsolową i zapakowaniu tego w zaplanowane zadania. Bo w ten sposób chodzi. Co tutaj może być nie tak?
Kombinuję teraz z Fiddlerem jeszcze.
Target framework: 4.6.1.
Dostawca bramki używa TLS1.2

0

Panowie @somekind i reszta "grubych" głów tutaj. Nie dyskutujcie tam o sealed tak dużo :)
Czy naprawdę nikt z Was nie potrafi, nie miał do czynienia z tym cudem?
Zapraszam tutaj.

0

A czemu client jest static?

0

próbuję już wielu opcji. Czytałem wiele treści w temacie poprawnego używania clienta. Jednym z nich był static. Ale nie jest on problemem w istocie.
Zrezygnowałem z "using" bo chodziło jeszcze gorzej a to rekomendowany sposób z Microsoftu.

Czy ktoś z Was mógłby to zreprodukować u siebie na 10?

0

Próbowałeś ustawić client.DefaultRequestHeaders.ConnectionClose = true;?

0

tak próbowałem. Ustawiłem to w metodzie, którą uruchamia Timer. Efekt wówczas jest taki, że już w 1szym "tiku" 1sza wiadomość leci, druga ma ten exception znienawidzony już przeze mnie .....
Zresztą ustawiałem też to w metodzie onStart() ten sam efekt.

0

Może to głupia sugestia, ale gdzieś mi się obiło na forach o oczy, że timer.Enabled = true to to samo co timer.Start() z tym że ten drugi ustawia timer.Enabled = true. Może przez to, że masz jedno i drugie coś ci się zazębia, a błąd o tym nie sugeruje

0

@AdamWox: masz rację jednak nie w tym tkwi problem.

0
john_doe napisał(a):

Robię to na Win 10 i szczerze pomału mam dość i zaczynam myśleć o zrobieniu tego jak zwykłą apkę konsolową i zapakowaniu tego w zaplanowane zadania. Bo w ten sposób chodzi. Co tutaj może być nie tak?

W takim razie spróbuj wydzielić logikę a następnie wywołać kod w serwisie i aplikacji konsolowej:

public class ServiceManager
    {
        public static readonly string API_URL = "https://api1.serwersms.pl/zdalnie/";
        public Dictionary<string, string> _content;
        public string _path = @"D:\log_service.txt";

        private readonly IMessageService _messageService;
        private readonly IMessageRepository _messageRepository;
        private static HttpClient client = new HttpClient();

        public ServiceManager(IMessageService messageService, IMessageRepository messageRepository)
        {
            _messageService = messageService;
            _messageRepository = messageRepository;
        }

        public async Task Process()
        {
            var _message = _messageRepository.GetByStatus(status.inQueue);

            {
                foreach (Message m in _message)
                {
                    _content = Services.Utils.SmsApiConfiguration.GetConfig();
                    _content.Add("wiadomosc", m.MessageBody);
                    _content.Add("numer", m.Phone);
                    _content.Add("akcja", "wyslij_sms");

                    var httpContent = new FormUrlEncodedContent(_content);

                    try
                    {
                        var response = await client.PostAsync(API_URL, httpContent);

                        if (response.IsSuccessStatusCode)
                        {
                            var responseContent = await response.Content.ReadAsStringAsync();
                            File.AppendAllText(_path, Environment.NewLine + " " + responseContent);
                        }
                    }
                    catch (Exception ex)
                    {
                        File.AppendAllText(_path, Environment.NewLine + " " + ex);
                    }
                }
            }
        }
    }

Konsoloa:

static async Task MainAsync(string[] args)
        {
            ServiceManager sm = CreateServiceManager();
            await sm.Process();

            await Task.Delay(10000);

            await sm.Process();
        }

Serwis:

public partial class Service1 : ServiceBase
    {
        private Timer timer;

        ServiceManager serviceManager;

        public Service1(ServiceManager serviceManager)
        {
            InitializeComponent();
            this.serviceManager = serviceManager;
        }

        protected override void OnStart(string[] args)
        {
            timer = new Timer(TimeSpan.FromMinutes(1).TotalMilliseconds);
            timer.AutoReset = true;
            timer.Enabled = true;
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Start();
        }

        private async void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            await serviceManager.Process();
        }

        protected override void OnStop()
        {
            timer.Stop();
            timer = null;
        }
    }
1

Używasz w timerze async await, więc timer nie czeka na zakończenie zadania w obsłudze zdarzenia dłużej niż przez Ciebie określona minuta we właściwości interval.
Może być tak, że pierwszy w ciągu pierwszej minuty nie zakończyła się obsługa wszystkich elementów w kolejce i ponowne wywołanie obsługi timera powoduje jakieś zakleszczenia.

Po każdorazowym uruchomieniu aplikacji konsolowej tworzony jest nowy Timer, nowy HttpClient, więc ma to prawo działać poprawnie.

0

najlepsze jest to, że na Windows 7 działa. Nie wiem jak na 8.1 ale na 10 takie kłopoty

0

na potrzeby posta tutaj spłaszczyłem moją aplikację pakując kod wysyłający do metody timera.
Generalnie, oryginalnie logikę mam rozdzieloną. Mimo to aplikacja konsolowa działa a serwis windowsowy nadal to samo.

0

zobaczcie proszę jak mam to w całości:
serwis:

public partial class Service1 : ServiceBase
    {
        private readonly IMessageService _messageService;
        private Timer timer;

        public Service1(IMessageService messageService)
        {
            InitializeComponent();
            _messageService = messageService;
        }

        protected override void OnStart(string[] args)
        {
            timer = new Timer(TimeSpan.FromMinutes(1).TotalMilliseconds);
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Start();
        }

        private async void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            var result = await _messageService.SendSms();
        }

        protected override void OnStop()
        {                      
        }
    }

serwis, który robi post request:

 public class SerwerSMSService : ISerwerSMSService
    {
        public static readonly string API_URL = "https://api1.serwersms.pl/zdalnie/";
        public Dictionary<string, string> _content;
        public string _path = @"D:\log_service.txt";

        private static HttpClient client = new HttpClient();

        public async Task<string> SendAsync(Message message)
        {

            _content = Utils.SmsApiConfiguration.GetConfig();
            _content.Add("wiadomosc", message.MessageBody);
            _content.Add("numer", message.Phone);
            _content.Add("akcja", "wyslij_sms");

            var httpContent = new FormUrlEncodedContent(_content);

            try
            {
                var response = await client.PostAsync(API_URL, httpContent);

                if (response.IsSuccessStatusCode)
                {
                    var responseContent = await response.Content.ReadAsStringAsync();

                    return responseContent;
                }
            }
            catch (Exception ex)
            {
                File.AppendAllText(_path, Environment.NewLine + " " + ex);
                return null;
            }

            return null;
        }
    }

oraz serwis, który wywołuje powyższą metodę

public class MessageService : IMessageService
    {
        public IEnumerable<Message> _message;

        private readonly IMessageRepository _messageRepository;
        private readonly ISerwerSMSService _serwerSMSService;

        public MessageService(IMessageRepository messageRepository, ISerwerSMSService serwerSMSService)
        {
            _messageRepository = messageRepository;
            _serwerSMSService = serwerSMSService;
        }

        public async Task<bool> SendSms()
        {
            bool ret = true;

            _message = _messageRepository.GetByStatus(status.inQueue);

            foreach (Message m in _message)
            {
                var result = await _serwerSMSService.SendAsync(m);
            
                if (result != null)
                {
                    var id = Utils.ParseXML.GetAttribValueByTagName(result, "id", "SMS");
                    m.Status = status.sent;
                    m.ResponseApiSmsId = id.Result;
                    m.ResponseApiSmsStan = "skolejkowano";

                }
                else
                {
                    m.Status = status.error;
                    m.ResponseApiSmsStan = "EX: SSL/TLS";
                }

                _messageRepository.Update(m);
            }
            
            return ret;
        }
    }

i nadal ten sam objaw. Wszystko leci po uruchomieniu usługi. Kolejne Elapsed timera - 1szy nie leci ZAWSZE.
dokładnie tutaj dostaję exception Żądanie zostało przerwane: Nie można utworzyć bezpiecznego kanału SSL/TLS.

var response = await client.PostAsync(API_URL, httpContent);

Nie wiem czy to pętla foreach i await w niej?
Próbowałem w metodzie Timera zatrzymać timer, uruchomić wysyłkę i ponownie włączyć timer. Ten sam objaw.
@somekind i inni cesarze C# chciałbym jednak aby mi to działało jako serwis.

0

Czy możemy zrobić z tego wątku ( moderatorze może do przeniesienia ) Ogłoszenie Drobne?
Proszę na priv informacje jeśli ktoś z Was chciałby się zająć tym przypadkiem

1 użytkowników online, w tym zalogowanych: 0, gości: 1