Таймер для отслеживания острых сообщений websocket в С#

Я использую dll websocket в своем приложении Windows для получения сообщений с сервера GDAX. Пока все работает нормально - сообщения приходят и я их обрабатываю. Точка, в которой я застрял, - это когда сообщения перестают приходить. По крайней мере, я ничего не нахожу в событии WebSocket.OnMessage(https://github.com/sta/websocket-sharp), которые могут помочь мне отслеживать, когда сообщения останавливаются (я также пробовал эмитировать)

Теперь сообщения, которые я получил, имеют тип сообщения «Сердцебиение», которое отправляется каждую секунду. Я хочу добавить отдельный элемент управления таймером, чтобы проверять, приходят ли сообщения пульса каждую секунду или нет, и если они перестанут приходить, мне нужно будет снова подключить сервер. Но поскольку ничего не происходит, когда сообщения перестают приходить, как мне это отслеживать, где я должен поместить код таймера, чтобы проверить, когда перестанут приходить сообщения пульса?

Я надеюсь, что смог объяснить ситуацию, в которой я поражен. Если кто-то хочет помочь мне и нуждается в дополнительных материалах, пожалуйста, дайте мне знать.

Обновить

    private void _3_Load(object sender, EventArgs e)
    {          
        ConnectAndGetWebsocketFeedMessages();           
    }

    public delegate void WSOpen(string text);
    public delegate void WSMessage(string message);
    public delegate void WSError(string text);
    public delegate void WSClose(string text);

    private static string _endPoint = "wss://ws-feed.gdax.com";
    WebSocket ws = new WebSocket(_endPoint);

    private bool IsConnected { get; set; }
    private string ProductId { get; set; }

    string productId = "LTC-EUR";
    ConcurrentQueue<string> concurrentQueue = new ConcurrentQueue<string>();

    public void SetWebSocketSharpEvents()
    {
        ws.Log.Level = LogLevel.Trace;

        ws.OnOpen += (sender, e) =>
        {
            IsConnected = true;
            OnWSOpen("Connection Status :: Connected *********");
        };
        ws.EmitOnPing = true;
        ws.OnMessage += (sender, e) =>
        {
            if (e.IsPing)
            {
                OnWSMessage("ping received");
            }
            else
            {                    
                OnWSMessage(e.Data);
            }
        };

        ws.OnError += (sender, e) =>
        {
            IsConnected = false;
            OnWSError(e.Message); //An exception has occurred during an OnMessage event. An error has occurred in closing the connection.
            if (ws.IsAlive)
                ws.Close();
        };

        ws.OnClose += (sender, e) =>
        {
            IsConnected = false;
            OnWSClose("Close");
        };

        ws.ConnectAsync();
    }

    private void ConnectAndGetWebsocketFeedMessages()
    {            
        SetWebSocketSharpEvents();
    }

    private void SubscribeProduct(string sProductID)
    {
        if (IsConnected)
        {
            ProductId = sProductID;
            string data = "{\"type\": \"subscribe\", \"product_ids\": [\"" + sProductID + "\"]}";
            ws.Send(data);
            ws.Send("{\"type\": \"heartbeat\", \"on\": true}");
        }
    }

    void OnWSOpen(string text)
    {
        SubscribeProduct(productId);
        timer1.Interval = 1000;
        timer1.Tick += timer1_Tick;
        timer1.Start();
    }

    DateTime lastHeartbeatTime = DateTime.MinValue;
    bool isTimerStart = false;
    void OnWSMessage(string message)
    {
        concurrentQueue.Enqueue(message);
        SaveHeartbeatMessageTime(message);
        ProcessMessage(message);
    }

    private void SaveHeartbeatMessageTime(string jsonString)
    {
        var jToken = JToken.Parse(jsonString);

        var typeToken = jToken["type"];

        var type = typeToken.ToString();

        if (type == "heartbeat")
        {
            lastHeartbeatTime = DateTime.Now;
            this.Invoke(new MethodInvoker(delegate()
            {
                lbllastheartbeat.Text = lastHeartbeatTime.ToLongTimeString();
            }));               
        }
    }

    private void ProcessMessage(string message) {  }

    void OnWSError(string text) { }

    void OnWSClose(string text) { }

    bool isMessagesReceived = false;

    private void timer1_Tick(object sender, EventArgs e) // it stops working as soon as lbllastheartbeat gets some value
    {
        DateTime currentTime = DateTime.Now;
        TimeSpan duration = currentTime.Subtract(lastHeartbeatTime);
        this.Invoke(new MethodInvoker(delegate()
        {
            lblNow.Text = currentTime.ToLongTimeString();
        }));
        if (Int16.Parse(duration.ToString("ss")) > 1)
        {
            // reconnect here
        }
    }

Изменить Я использую элемент управления таймером формы Windows, и он продолжает вызывать метод timer1_Tick и не вызывает метод OnWSMessage. Как я могу гарантировать, что оба работают параллельно, и если какое-либо сообщение пропущено или сообщение перестает поступать, оно снова подключается?

Edit2 В приведенных ниже решениях предлагается добавить функцию таймера в событие onMessage, но что произойдет, если я не буду получать сообщения? Если сообщения не получены, код ничего не делает. Я взял глобальную переменную, и всякий раз, когда приходит сообщение, оно добавляет время в эту переменную. Теперь я хочу запустить отдельный элемент управления таймером, который будет проверять, есть ли что-нибудь в этой переменной, и если ее значение, то есть разница в секундах, больше 1, то продолжайте проверять что-то еще.

Есть ли кто-нибудь, кто может изучить это и посоветовать, пожалуйста.

Update2: я все еще хочу сделать это с помощью элемента управления windows.timer, а не threading.timer. Я взял две метки в своем приложении для Windows: lbllastheartbeat (чтобы показать время, когда получено сообщение пульса) и lblNow (чтобы показать текущее время, когда вызывается таймер).

Требование. Мой таймер будет проверять, не пропущено ли какое-либо сообщение пульса, и это делается с помощью переменной lastHeartbeatTime, в которой хранится время получения сообщения пульса.

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


person user1254053    schedule 07.11.2017    source источник
comment
Если я вас правильно понял, у вас есть событие OnMessage, которое возникает при получении сообщения. В этом случае вы можете остановить таймер, обработать сообщение и перезапустить таймер. Если вы установите таймер Interval на одну секунду (или любое другое максимальное время между сообщениями, указывающее на то, что сообщения не были отправлены), то событие таймера Elapsed (или Tick) будет таким, когда вы будете знать, что ни одно сообщение не было получено в течение одной секунды. второй.   -  person Rufus L    schedule 07.11.2017
comment
@Rufus L- не могли бы вы взглянуть на мой код и сообщить, почему мое событие timer1_tick вызывается бесконечно и не достигает события onmessage   -  person user1254053    schedule 13.11.2017
comment
Мое решение ниже также сбрасывает таймер в OnOpen. Таким образом, он обнаружит, нет ли сообщений в течение 2 секунд, даже если сообщений еще не было.   -  person Evk    schedule 15.11.2017
comment
Open работает только в первый раз.. так что да, это сработает, но я говорю о ситуации, когда сообщения приходят и внезапно прекращаются.. В этой ситуации событие onmessage не запускается, поэтому оно не будет вызывать 'ResetTimeoutTimer()' .   -  person user1254053    schedule 15.11.2017
comment
Я был бы признателен, если бы вы могли помочь/посоветовать мне с моим событием timer1_tick.   -  person user1254053    schedule 15.11.2017
comment
ResetTimeoutTimer, как следует из его названия, сбрасывает таймер, поэтому он начинает тикать и запускает обратный вызов через X секунд (2 секунды в моем примере). Когда вдруг перестают приходить сообщения - ничего не вызывает ResetTimeoutTimer и это хорошо, ведь в этом вся суть. Когда ничего не сбрасывает таймер - он, наконец, запускает свой обратный вызов. Когда срабатывает обратный вызов, вы знаете, что вы не получали сообщений в течение последних 2 секунд, и поэтому вам следует переподключиться.   -  person Evk    schedule 15.11.2017


Ответы (2)


Ответ уже дан - вам нужно запустить таймер, который будет срабатывать после вашего тайм-аута, когда вы получите сообщение, и сбрасывать этот таймер каждый раз, когда вы получаете сообщение. Но, похоже, вам нужен пример кода, вот он (с комментариями):

System.Threading.Timer _timeoutTimer;
private readonly object _timeoutTimerLock = new object();
private void ResetTimeoutTimer() {
    // if you are sure you will never access this from multiple threads at the same time - remove lock
    lock (_timeoutTimerLock) {
        // initialize or reset the timer to fire once, after 2 seconds
        if (_timeoutTimer == null)
            _timeoutTimer = new System.Threading.Timer(ReconnectAfterTimeout, null, TimeSpan.FromSeconds(2), Timeout.InfiniteTimeSpan);
        else
            _timeoutTimer.Change(TimeSpan.FromSeconds(2), Timeout.InfiniteTimeSpan);
    }
}

private void StopTimeoutTimer() {
    // if you are sure you will never access this from multiple threads at the same time - remove lock
    lock (_timeoutTimerLock) {
        if (_timeoutTimer != null)
            _timeoutTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
    }
}

private void ReconnectAfterTimeout(object state) {
    // reconnect here
}

public void SetWebSocketSharpEvents() {
    ws.Log.Level = LogLevel.Trace;

    ws.OnOpen += (sender, e) => {
        // start timer here so that if you don't get first message after 2 seconds - reconnect
        ResetTimeoutTimer();
        IsConnected = true;
        OnWSOpen("Connection Status :: Connected *********");
    };
    ws.EmitOnPing = true;
    ws.OnMessage += (sender, e) => {
        // and here
        ResetTimeoutTimer();
        if (e.IsPing) {
            OnWSMessage("ping received");
        }
        else {
            OnWSMessage(e.Data);
        }
    };

    ws.OnError += (sender, e) => {
        // stop it here
        StopTimeoutTimer();
        IsConnected = false;
        OnWSError(e.Message); //An exception has occurred during an OnMessage event. An error has occurred in closing the connection.


  if (ws.IsAlive)
                ws.Close();
        };

        ws.OnClose += (sender, e) => {
            // and here
            StopTimeoutTimer();
            IsConnected = false;
            OnWSClose("Close");
        };

        ws.ConnectAsync();
    }
person Evk    schedule 09.11.2017
comment
Спасибо Евк за объяснение. Я получаю сообщение об ошибке: «Таймер» является неоднозначной ссылкой между «System.Threading.Timer» и «System.Windows.Forms.Timer». Как решить эту проблему? - person user1254053; 09.11.2017
comment
@ user1254053 замените new Timer на new System.Threading.Timer. - person Evk; 09.11.2017
comment
Evk - я пытаюсь запустить ваш код, но не могу вызвать timercallback таймера. Для этого я создал отдельную тему. Не могли бы вы проверить здесь - stackoverflow .com/questions/47455037/ - person user1254053; 23.11.2017

из вашего вопроса я понимаю, что ваше сообщение отправляется через каждые секунды, но проблема заключается только в том, что когда оно останавливается, вы хотите знать и запускать его снова, если это так, вы просто применяете таймер и проверяете каждую секунду, если сообщение не отправлено через секунду или более (проверьте метод sentMessage(), установив логическое значение, если сообщение отправлено, оно должно дать true, иначе false), чем дать команду для повторного подключения к серверу.

person Muhammad Ali    schedule 07.11.2017
comment
Спасибо Мухаммед Али за ответ. Как применить таймер? Я попробовал то, что вы предложили, но это продолжается в бесконечном цикле события тикера. - person user1254053; 08.11.2017
comment
Таймер прост, timer.Start(), поэтому он запускает время, поэтому вам нужно будет проверить, правильно ли работает ваш метод, проверяя через секунду или что угодно. - person Muhammad Ali; 08.11.2017