Прерывание задачи, содержащей бесконечный библиотечный метод (нет возможности проверки запросов на отмену)

Я использую Pcap.Net для мониторинга трафика, и мне нужно, чтобы он принимал пакеты, пока пользователь не запросит отмену. Я создаю задачу мониторинга следующим образом (упрощенно):

var task1 = Task.Run(() => { communicator.ReceivePackets(0, PacketHandlerCallback); } /*, token*/);

Здесь 0 говорит, что выполнение ReceivePackets никогда не заканчивается, PacketHandlerCallback — это метод, который будет выполняться для каждого полученного пакета. ReceivePackets является синхронным и не поддерживает отмену. Вообще в моем вопросе это может быть любой другой бесконечный синхронный метод, код которого мы не можем редактировать.

Вопрос в том, как остановить выполнение этого метода?

  • Просто передача маркера отмены в задачу не помогает, потому что мы также должны явно проверять, запрошена ли отмена, например. г. по телефону token.throwIfCancellationRequested().

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

  • Использование BackgroundWorker вызывает тот же вопрос, так как мы должны проверить CancellationPending.

  • Создание task2, которое периодически проверяет запросы на отмену, а затем запись var task = Task.WhenAny(task1, task2) не помогает, поскольку ReceivePackets все равно будет выполняться.

Должен ли я использовать Thread.Abort() или, может быть, есть какое-то другое элегантное решение?
Есть похожие вопросы о TPL на SO, но я не могу найти простого и полезного ответа.


person N. Kudryavtsev    schedule 03.06.2017    source источник
comment
Возможно, вы захотите установить communicator.NonBlocking = true, а затем переписать свой код, чтобы вместо этого использовать communicator.ReceiveSomePackets в неблокирующем режиме, тогда вам не придется беспокоиться о зависании кода, и вы можете просто избавиться от коммуникатора, когда захотите все очистить.   -  person Scott Chamberlain    schedule 03.06.2017
comment
@ Скотт Отличное предложение, спасибо. Я знаю, что вопрос не в этом, но это именно то, что мне сейчас нужно. Не могли бы вы подтвердить, что если я напишу while(true) { communicator.ReceiveSomePackets(/*args*/); token.throwIfCancellationRequested(); } для неблокирующего коммуникатора, я не потеряю пакеты, которые могут быть получены при проверке запроса на отмену?   -  person N. Kudryavtsev    schedule 03.06.2017
comment
Никогда раньше не пользовался этой библиотекой, я только что проверил источник и увидел свойство и примечания к свойству.   -  person Scott Chamberlain    schedule 03.06.2017


Ответы (3)


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

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

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

person Scott Chamberlain    schedule 03.06.2017

Обычно Thread.Abort действительно не следует использовать как очень устаревший и опасный метод. Однако ваша ситуация выглядит так, что вам нужно убить поток/процесс, чтобы остановить бесконечный метод.

Я предлагаю вам избегать прерывания любого из ваших потоков, так как это может привести к нестабильной работе системы, поскольку прерванный поток не сможет правильно очистить ресурсы. Вы можете запустить свой метод в новом AppDomain и выгрузить этот домен в случае запроса на отмену. Кроме того, как упомянул Скотт, отдельный Process тоже является решением.

Если и только если по какой-то причине это не вариант для вас, вы можете подписаться на отмену вашего токена с помощью Thread.CurrentThread.Abort, но на вашем месте я бы избегал этого варианта, насколько мог.

Также вы можете создать задачу из токена отмены и используйте WhenAll для асинхронного ожидания отмены.

person VMAtm    schedule 03.06.2017

Есть PacketCommunicator.Break() метод.

Из кода документации:

/// <summary>
/// Set a flag that will force ReceiveSomePackets(), ReceivePackets() or ReceiveStatistics() to return rather than looping.
/// They will return the number of packets/statistics that have been processed so far, with return value BreakLoop.
/// <seealso cref="ReceiveSomePackets"/>
/// <seealso cref="ReceivePackets"/>
/// <seealso cref="ReceiveStatistics(int, HandleStatistics)"/>
/// </summary>
/// <remarks>
///   <list type="bullet">
///     <item>This routine is safe to use inside a signal handler on UNIX or a console control handler on Windows, as it merely sets a flag that is checked within the loop.</item>
///     <item>The flag is checked in loops reading packets from the OS - a signal by itself will not necessarily terminate those loops - as well as in loops processing a set of packets/statistics returned by the OS.</item>
///     <item>Note that if you are catching signals on UNIX systems that support restarting system calls after a signal, and calling Break() in the signal handler, you must specify, when catching those signals, that system calls should NOT be restarted by that signal. Otherwise, if the signal interrupted a call reading packets in a live capture, when your signal handler returns after calling Break(), the call will be restarted, and the loop will not terminate until more packets arrive and the call completes.</item>
///     <item>ReceivePacket() will, on some platforms, loop reading packets from the OS; that loop will not necessarily be terminated by a signal, so Break() should be used to terminate packet processing even if ReceivePacket() is being used.</item>
///     <item>Break() does not guarantee that no further packets/statistics will be processed by ReceiveSomePackets(), ReceivePackets() or ReceiveStatistics() after it is called; at most one more packet might be processed.</item>
///     <item>If BreakLoop is returned from ReceiveSomePackets(), ReceivePackets() or ReceiveStatistics(), the flag is cleared, so a subsequent call will resume reading packets. If a different return value is returned, the flag is not cleared, so a subsequent call will return BreakLoop and clear the flag.</item>
///   </list>
/// </remarks>
person brickner    schedule 09.06.2017