3

I use Pcap.Net for traffic monitoring, and I need it to receive packets until user requests cancellation. I create monitoring task this way (simplified):

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

Here 0 says execution of ReceivePackets never ends, PacketHandlerCallback is a method that will be executed for each received packet. ReceivePackets is synchronous and does not support cancellation. Generally in my question it could be any other endless synchronous method which code we cannot edit.

The question is how to stop this method execution?

  • Just passing cancellation token to the task doesn't help because we should also explicitly check if cancellation is requested, e. g. by calling token.throwIfCancellationRequested().

  • Passing token to callback method is not a solution too because this method will not be invoked until new packet is received but I'd like to stop my task immediately after cancellation.

  • Using BackgroundWorker causes the same question since we should check CancellationPending.

  • Creating task2 that periodically checks cancellation requests and then writing var task = Task.WhenAny(task1, task2) doesn't help since ReceivePackets will still be executing.

Should I use Thread.Abort() or maybe there is any other elegant solution?
There are similar questions about TPL on SO but I cannot find any simple and helpful answer.

VMAtm
  • 27,943
  • 17
  • 79
  • 125
N. Kudryavtsev
  • 3,556
  • 1
  • 26
  • 30
  • You may want to consider setting `communicator.NonBlocking = true` and then rewriting your code to use `communicator.ReceiveSomePackets` in the non blocking mode instead, then you don't have to worry about code getting hung up and you can just dispose of the communicator when you want everything cleaned up. – Scott Chamberlain Jun 03 '17 at 19:23
  • @Scott Great suggestion, thank you. I know it is not what is the question about but it is exactly what I need now. Could you please confirm that if I write `while(true) { communicator.ReceiveSomePackets(/*args*/); token.throwIfCancellationRequested(); }` for non-blocking communicator I will not lose packets that can be received during cancellation request checking? – N. Kudryavtsev Jun 03 '17 at 19:59
  • Never used the library before, i just checked the source and saw the property and the notes with the property. – Scott Chamberlain Jun 03 '17 at 20:34

3 Answers3

1

Usually the Thread.Abort really shouldn't be used as very outdated and dangerous method. However, your situation looks like that you have to kill the thread/process to stop the infinite method.

I suggest you to avoid the aborting for any of your threads, as this can lead you to unstable system, as aborted thread wouldn't be able to clean resources correctly. You may run your method in new AppDomain, and unload that domain in case of cancellation request. Also, as Scott mentioned, a separate Process is a solution too.

If and only if, so some reason this isn't an option for you, you can subscribe for cancellation of your token with Thread.CurrentThread.Abort, but if I were you, I would avoid this option as much as I could.

Also you may create a task from cancellation token and use WhenAll for asynchronously wait for cancellation.

VMAtm
  • 27,943
  • 17
  • 79
  • 125
1

In situations where you can't cancel the much better solution than Thread.Abort is to put the non-cancelable code in to a separate process you can kill.

This gives you a guaranteed release of all resources the thread held because the OS will release any held unmanaged OS resources like handles when a process exits, you don't get this behavior if you abort a thread or if you use a separate AppDomain you shutdown.

When you have a 2nd process written you can use something like WCF over named pipes so you can interact with the external process like you would any other normal function that is inside your process.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
1

There's PacketCommunicator.Break() method.

From the documentation code:

/// <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>
brickner
  • 6,595
  • 3
  • 41
  • 54