Страницы

Поиск по вопросам

понедельник, 15 июля 2019 г.

CancellationToken для задачи обертки?

Доброго времени суток!!! Использую в проекте библиотеку для чтения по протоколу Modbus rtu. Но обнаружил что в нее не передается параметр время ожидания и/или CancellationToken. Что должно быть обязательно в таких блокирующих на долго поток задачах. ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints);
Решил сделать обертку и отменять задачу по истечении времени.
public async Task ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct) { return await await Task.Factory.StartNew(async () => { Task task = _master.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints);
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(); using (ct.Register(() => tcs.SetResult(0))) await tcs.Task; }
public async Task ReadHoldingRegistersAsync( byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct) { var mainTask = _master.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints); var cancellationWaitTask = WaitCancellation(ct);
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. Но это может быть и невозможно, например, если целевое устройство не сумеет после обрыва коммуникации посередине передачи информации восстановить её без специальных на то команд.

Комментариев нет:

Отправить комментарий