我正在使用C#和套接字为类分配编写一个非常简单的RPC库。我的发送功能正常工作,但是我正在测试KillServer函数的工作方式。我已经在服务器库中实现了很多线程,但是我不确定是否掌握了取消和软终止线程的正确方法。
我的StartServerThread方法:
public void StartServer()
{
ServerThread = new Task(()=>
{
_Socket.Listen(100);
// Enter a loop waiting for connection requests
// Or because I am RDM I don't need connections
while (!_TokenSource.IsCancellationRequested)
{
try
{
var newClient = _Socket.Accept();
_Clients.Add(newClient, ClientThread(newClient));
}
catch (OperationCanceledException)
{
Debug.WriteLine("Canceled");
}
}
foreach (var client in _Clients)
{
client.Key.Disconnect(false);
}
},_TokenSource.Token, TaskCreationOptions.LongRunning);
ServerThread.Start();
}
我的问题是,一旦线程碰到_Socket.Accept()行,它就会阻塞。因此,即使我在TokenSource上调用Cancel,该线程仍然停留在该行,并且不会很好地结束。我在设计这种库时遇到了麻烦,该方法使用线程并在连接多个客户端的情况下具有良好的性能。
最佳答案
有几种解决方法。您可以使用已经提到的非阻塞套接字,也可以使用诸如Socket.BeginAccept
的异步函数。这是来自MSDN的example。
重要的代码如下。
while (true)
{
// Set the event to nonsignaled state.
allDone.Reset();
listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
// Wait until a connection is made before continuing.
allDone.WaitOne();
}
在此处,当接受新的TCP连接时,将在回调函数中设置手动重置事件
allDone
,该连接会在循环前一个代码以再次调用Socket.BeginAccept
时唤醒。public static void AcceptCallback(IAsyncResult ar)
{
// Signal the main thread to continue.
allDone.Set();
// Get the socket that handles the client request.
Socket listener = (Socket) ar.AsyncState;
Socket handler = listener.EndAccept(ar);
...
现在,如果要停止线程接受新的调用,可以“滥用”事件
allDone
并将其设置为唤醒正在等待事件allDone
的“BeginAccept”线程。然后,'BeginAccept'线程可以检查TokenSource.IsCancellationRequested
,是否应该继续循环周期或退出循环。// MODIFY while contition.
while (!_TokenSource.IsCancellationRequested)
{
// Set the event to nonsignaled state.
allDone.Reset();
listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
// Wait until a connection is made before continuing.
allDone.WaitOne();
}
这是您要退出“BeginAccept”线程时调用的代码。
// Don't continue with the while cycle.
_TokenSource.Cancel();
// Wake up thread calling BeginAccept().
allDone.Set()
另一种可能性是使用2个事件实例。这样的事情。
// Declared somewhere to be accessible from both threads.
ManualResetEvent exitThread;
while (true)
{
// Set the event to nonsignaled state.
allDone.Reset();
listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
// Wait until a connection is made or thread is exiting
if (WaitHandle.WaitAny(new WaitHandle[]{exitThread,allDone})==0)
{
break;
}
}
现在,可以通过执行停止从第二个线程开始的循环。
exitThread.Set();
代码
WaitHandle.WaitAny()
正在等待,直到发出2个事件中的任何一个,并且当exitThread
发出信号(WaitHandle.Wait()
返回0)时,它跳出循环。但是现在出现了另一个问题。因为您在调用
Socket.BeginAccept
时已经调用了Socket.Close
,所以会触发回调AcceptCallback
。并且因为套接字已经被关闭,所以方法Socket.EndAccept
将抛出ObjectDisposedException
。因此,您必须在回调方法AcceptCallback
中捕获并忽略此异常。这里还有更多关于Socket.BeginReceive
的details,它也适用于Socket.BeginAccept
。随着.NET Task库的到来,通过使用扩展方法AcceptAsync,可能还有另一个更优雅的解决方案,但是我在Internet上找不到一些很好的示例。我个人更喜欢“老式”的BeginXYZ()-EndXYZ()异步模型-也许是因为我太老了...
关于c# - C#套接字服务器如何取消卡在socket.accept()中的线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52283776/