Токен отмены отменяется, закрывая TCP-сокет.

Вопрос или проблема

У меня есть очень простое приложение, которое пытается отправлять и получать данные.

public async Task SendDataAsync(bytes[] data, CancellationToken ct)
{
    //Отправка данных
    await TcpClient.GetStream().WriteAsync(bytes, ct);

    //Чтение ответа
    var buffer = new byte[256];
    var bytesRead = await TcpClient.GetStream().ReadAsync(buffer, ct);
    if (bytesRead == 0)
    {
        Disconnect();
        //Что-то пошло не так
        throw new ModbusExceptions.ConnectionException("Получено 0 байт, сокет был закрыт");
    }
}

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

Однако сейчас у нас есть случай, когда TCP клиент, от которого мы пытаемся прочитать данные, в некоторых сценариях не отвечает (то есть получает наши данные, т.е. запись работает), но не отвечает.

В этом случае я буду ждать навсегда на своей линии получения.

Поэтому я теперь добавил тайм-аут, используя токен отмены, на 5 секунд.

using (var ct = new CancellationTokenSource(5000))
{
    await SendDataAsync(bytes, ct.Token);
}

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

Поэтому, если я попробую вызвать await SendDataAsync(bytes, ct.Token); во второй раз, возникает исключение, которое говорит, что вы не можете выполнить эту операцию на неоткрытом сокете.

Я что-то упускаю?

Разве если запись в поток была отменена токеном, мне не нужно восстанавливать новый TCP сокет с клиентом?

В моем случае я просто хочу подождать 5000 мс и затем снова попробовать отправить следующее сообщение?

Ответ или решение

Ваш вопрос касается использования токенов отмены при работе с TCP-сокетами в асинхронном программировании. Давайте рассмотрим ваши проблемы и предложим возможные решения.

Проблема

Вы добавили токен отмены в свой метод SendDataAsync, чтобы избежать ситуаций, когда программа зависает, ожидая ответ от сервера. Однако при отмене токена сокет закрывается, что делает его недоступным для последующих операций. Вы хотите иметь возможность повторно отправлять сообщения без необходимости повторного создания TCP-соединения.

Причины проблемы

При отмене токена в методах WriteAsync или ReadAsync, библиотека, с которой вы работаете, закрывает поток, на который ссылается ваш сокет. Это связано с тем, что в случае отмены операции подразумевается, что сокет может находиться в некорректном состоянии, и его нельзя повторно использовать.

Возможные решения

  1. Создание нового TCP-сокета при каждой отмене:
    Это кажется неэффективным и может быть непрактичным, особенно если вы часто отменяете операции. Но это можно рассмотреть как решение в случае, если приложение ожидает частых отмен.

  2. Обработка ошибок и восстановление сокета:
    Вместо создания нового сокета, вы можете добавить логику обработки ошибок. Если возникает исключение из-за того, что сокет закрыт, вы можете инициализировать новое соединение:

private async Task<bool> TrySendDataAsync(byte[] data, CancellationToken ct)
{
    try
    {
        using (var ctSource = new CancellationTokenSource(5000))
        {
            await SendDataAsync(data, ctSource.Token);
            return true; // Успех
        }
    }
    catch (IOException ex)
    {
        // Здесь можно добавить логику для восстановления сокета при возникновении проблем
        // Например, вы можете создать и открыть новый TcpClient
        // TcpClient = new TcpClient();
        // await TcpClient.ConnectAsync("hostname", port);
        return false; // Ошибка
    }
}

// И вызов этого метода
if (!await TrySendDataAsync(bytes, new CancellationToken()))
{
    // Обработайте неудачу (например, попробуйте снова через несколько секунд)
}
  1. Использование Task.WhenAny:
    Вы также можете рассмотреть возможность использования Task.WhenAny, чтобы отслеживать, истекло ли время ожидания, и затем иметь возможность еще раз попробовать отправить данные.
public async Task SendDataWithTimeoutAsync(byte[] data)
{
    using (var ctSource = new CancellationTokenSource(5000))
    {
        var sendTask = SendDataAsync(data, ctSource.Token);
        var delayTask = Task.Delay(5000, ctSource.Token);

        var completedTask = await Task.WhenAny(sendTask, delayTask);

        if (completedTask == delayTask)
        {
            // Обработка таймаута: возможно, вы хотите перезапустить соединение или сообщить об ошибке
            throw new TimeoutException("Sending data timed out.");
        }

        await sendTask; // Дождаться завершения sendTask
    }
}

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...