Цикл не остановится с Thread и CancellationToken
Я работаю с приложением сокета Windows, используя асинхронные обратные вызовы. Если я использую Thread для запуска _StartListening
когда я звоню StopListening
цикл все еще останавливается на allDone.WaitOne()
, Но версия задачи будет в порядке.
Какая разница?
Мой код является модифицированной версией этого
Оригинальная версия с ManualResetEvent
имеет состояние гонки, упомянутое Феликсом-б. Я изменил это на SemaphoreSlim
но проблема все еще там.
Я пытался в режиме отладки, и кажется, что точка останова никогда не будет достигнута if (cancelToken.IsCancellationRequested)
после того, как я позвоню StopListening
даже я не запускаю клиента.
Сожалею. Я обнаружил, что случайно запустил два сокета-сервера. Это проблема.
class WinSocketServer:IDisposable
{
public SemaphoreSlim semaphore = new SemaphoreSlim(0);
private CancellationTokenSource cancelSource = new CancellationTokenSource();
public void AcceptCallback(IAsyncResult ar)
{
semaphore.Release();
//Do something
}
private void _StartListening(CancellationToken cancelToken)
{
try
{
while (true)
{
if (cancelToken.IsCancellationRequested)
break;
Console.WriteLine("Waiting for a connection...");
listener.BeginAccept(new AsyncCallback(AcceptCallback),listener);
semaphore.Wait();
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Console.WriteLine("Complete");
}
public void StartListening()
{
Task.Run(() => _StartListening(cancelSource.Token));//OK
var t = new Thread(() => _StartListening(cancelSource.Token));
t.Start();//Can't be stopped by calling StopListening
}
public void StopListening()
{
listener.Close();
cancelSource.Cancel();
semaphore.Release();
}
public void Dispose()
{
StopListening();
cancelSource.Dispose();
semaphore.Dispose();
}
}
1 ответ
Ваш код имеет состояние гонки, которое может привести к тупику (иногда). Давайте назовем темы "слушатель" (тот, который работает _StartListening
) и "контроль" (тот, который работает StopListening
):
- Поток слушателя:
if (cancelToken.IsCancellationRequested)
-> ложь - Контрольная нить:
cancelSource.Cancel()
- Контрольная нить:
allDone.Set()
- Поток слушателя:
allDone.Reset()
-> случайно сбрасывает запрос на остановку! - Поток слушателя:
listener.BeginAccept(...)
- Контрольная нить:
stopListening()
выходит, а слушатель продолжает работать! - Поток слушателя:
allDone.WaitOne()
-> тупик! никто не сделаетallDone.Set()
,
Проблема в том, как вы используете allDone
Событие должно быть наоборот: _StartListening
следует сделать allDone.Set()
прежде чем он выйдет по какой-либо причине, тогда как StopListening
следует сделать allDone.WaitOne()
:
class WinSocketServer:IDisposable
{
// I guess this was in your code, necessary to show proper stopping
private Socket listener = new Socket(......);
public ManualResetEvent allDone = new ManualResetEvent(false);
private CancellationTokenSource cancelSource = new CancellationTokenSource();
private void _StartListening(CancellationToken cancelToken)
{
try
{
listener.Listen(...); // I guess
allDone.Reset(); // reset once before starting the loop
while (!cancelToken.IsCancellationRequested)
{
Console.WriteLine("Waiting for a connection...");
listener.BeginAccept(new AsyncCallback(AcceptCallback),listener);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
allDone.Set(); // notify that the listener is exiting
Console.WriteLine("Complete");
}
public void StartListening()
{
Task.Run(() => _StartListening(cancelSource.Token));
}
public void StopListening()
{
// notify the listener it should exit
cancelSource.Cancel();
// cancel possibly pending BeginAccept
listener.Close();
// wait until the listener notifies that it's actually exiting
allDone.WaitOne();
}
public void Dispose()
{
StopListening();
cancelSource.Dispose();
allDone.Dispose();
}
}
ОБНОВИТЬ
Стоит отметить, что listener.BeginAccept
не вернется, пока не установится новое клиентское соединение. При остановке слушателя необходимо закрыть сокет (listener.Close()
) заставить BeginAccept
выходить.
Разница в поведении Thread/Task действительно странная, она, вероятно, может быть вызвана тем, что поток Task является фоновым потоком, тогда как обычный поток является приоритетным.