Cancel blocking AcceptTcpClient call

后端 未结 5 1486
生来不讨喜
生来不讨喜 2020-12-09 18:00

As everyone may already know, the simplest way to accept incoming TCP connections in C# is by looping over TcpListener.AcceptTcpClient(). Additionally this way will block co

5条回答
  •  粉色の甜心
    2020-12-09 18:42

    Well, in the olden days before properly working asynchronous sockets (the best way today IMO, BitMask talks about this), we've used a simple trick: set the isRunning to false (again, ideally, you want to use CancellationToken instead, public static bool isRunning; is not a thread-safe way to terminate a background worker :)) and start a new TcpClient.Connect to yourself - this will return you from the Accept call and you can terminate gracefully.

    As BitMask already said, Thread.Abort most definitely isn't a safe approach at termination. In fact, it wouldn't work at all, given that Accept is handled by native code, where Thread.Abort has no power. The only reason it works is because you're not actually blocking in the I/O, but rather running an infinite loop while checking for Pending (non-blocking call). This looks like a great way to have 100% CPU usage on one core :)

    Your code has a lot of other issues too, which don't blow up in your face only because you're doing very simple stuff, and because of .NET being rather nice. For example, you're always doing GetString on the whole buffer you're reading into - but that's wrong. In fact, that's a textbook example of a buffer overflow in e.g. C++ - the only reason it seems to work in C# is because it pre-zeroes the buffer, so GetString ignores the data after the "real" string you read. Instead, you need to take the return value of the Read call - that tells you how many bytes you've read, and as such, how many you need to decode.

    Another very important benefit of this is it means you no longer have to recreate the byte[] after each read - you can simply reuse the buffer over and over again.

    Don't work with the GUI from other thread than the GUI thread (yes, your Task is running in a separate thread pool thread). MessageBox.Show is a dirty hack that in fact does work from other threads, but that really isn't what you want. You need to invoke the GUI actions on the GUI thread (for example using Form.Invoke, or by using a task that has a synchronization context on the GUI thread). That will mean the message box will be the proper dialog you'd expect.

    There's many more issues with the snippet you posted, but given that this isn't Code Review, and that it's an old thread, I'm not going to make this any longer :)

提交回复
热议问题