Существует несколько способов решения этой проблемы. Вы можете использовать уже упомянутые неблокирующие сокеты или асинхронные функции, такие как Socket.BeginAccept
. Вот пример из MSDN.
Ниже приведен важный код.
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();
}
Здесь событие ручного сброса allDone
устанавливается в функции обратного вызова, когда принимается новое TCP-соединение, которое активируется во время цикла в предыдущем коде для повторного вызова 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
и настроить его на пробуждение вашего потока «BeginAccept», который ожидает событие allDone
. Затем поток '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
, что относится и к Socket.BeginAccept
.
С появлением библиотеки .NET Task есть еще одно, возможно, более элегантное решение с использованием метода расширения Accept Async , но я не могу найти хороший пример в Интернете. И я лично предпочитаю асинхронную модель BeginXYZ () - EndXYZ () старой школы - возможно, потому что я слишком стар ...