Доброго времени суток!!!
Использую в проекте библиотеку для чтения по протоколу Modbus rtu.
Но обнаружил что в нее не передается параметр время ожидания и/или CancellationToken.
Что должно быть обязательно в таких блокирующих на долго поток задачах.
ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints);
Решил сделать обертку и отменять задачу по истечении времени.
public async Task
while (!task.IsCompleted)
{
ct.ThrowIfCancellationRequested();
}
return await task;
}, ct);
}
Где вначале создаю горячую задачу и потом пока задача не завершилась проверяю состояние токена отмены.
Вызывающий код:
using (var cts = new CancellationTokenSource(timeRespoune)) //время на ожидание ответа
{
try
{
byte[] sendBuffer = dataProvider.GetDataByte();
if (sendBuffer == null || !sendBuffer.Any()) //READ
{
var takeBuff = await ReadHoldingRegistersAsync(slaveAddress, startAddress, (ushort)(dataProvider.CountGetDataByte / 2), cts.Token);
}
else
_countTryingTakeData = 0;
}
catch (OperationCanceledException)
{
StatusString = string.Format("Время на ожидание ответа вышло");
if (++_countTryingTakeData > numberTryingTakeData)
Connect();
}
}
Все это вызывается в цикле опроса, т.е. ждем данные N секунд если ответа нету то по OperationCanceledException выходим из блока чтения, и повторяем заново.
ПРОБЛЕМА:
Смотрю в отладчике и система с токеном отмены работает, но данные в порт отправляются 1 раз. Т.е. статус таска запущен но в порту ничего нету. Прерывается по OperationCanceledException заходит заново формирует новый таск (новый id) заходит в цикл проверки отмены но данных в порту нету. Это что то я не так делаю или библиотека такая?
Ответ
Для начала, у вас busy waiting: цикл
while (!task.IsCompleted)
{
ct.ThrowIfCancellationRequested();
}
должен потреблять впустую довольно много ресурсов, постоянно переспрашивая состояние Task'а. Есть смысл переписать эту логику более дружественным к процессору способом:
async Task WaitCancellation(CancellationToken ct)
{
var tcs = new TaskCompletionSource
public async Task
var firstFinishedTask = await Task.WhenAny(mainTask, cancellationWaitTask);
if (firstFinishedTask == cancellationWaitTask)
ct.ThrowIfCancellationRequested();
return await mainTask;
}
Заодно и Task.Factory.StartNew не понадобилось.
Теперь по делу. Отменить бегущую операцию снаружи сложно, потому что она должна по окончанию подчистить внутренние структуры данных. Вы по сути бросаете бегущий таск на произвол судьбы, и параллельно ему начинаете следующий. Это может оказаться не вполне верной идея, т. к. параллельный таск будет наверняка пользоваться теми же структурами данных, и два таска будут мешать друг другу. (Ну тут лучше, конечно, поинтересоваться у авторов библиотеки.)
В вашем случае, судя по всему, вы пользуетесь библиотекой, которая пишет в порт (serial?), так что вы можете «жёстко» оборвать таск, просто закрыв порт. У SerialPort можно вызвать Dispose, при этом операция внутри ReadHoldingRegistersAsync завершится аварийно с исключением, объект _master окажется в плохом состоянии, так что вам придётся пересоздать _master снова. Это может быть не таким уж плохим решением (я пользовался им для управления USB-устройством через SerialPort).
В этом случае ваш внешний код должен по приходу OperationCanceledException (или что там будет выброшено) закрыть текущий _master и пересоздать его снова. Или найти несущий SerialPort, закрыть его, поймать исключение из его последней операции, и наконец закрыть текущий _master и пересоздать его снова.
Разумеется, самым чистым и правильным решением было бы наличие поддержки CancellationToken в ReadHoldingRegistersAsync. Но это может быть и невозможно, например, если целевое устройство не сумеет после обрыва коммуникации посередине передачи информации восстановить её без специальных на то команд.
Комментариев нет:
Отправить комментарий