Цикл не остановится с 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):

  1. Поток слушателя: if (cancelToken.IsCancellationRequested) -> ложь
  2. Контрольная нить: cancelSource.Cancel()
  3. Контрольная нить: allDone.Set()
  4. Поток слушателя: allDone.Reset() -> случайно сбрасывает запрос на остановку!
  5. Поток слушателя: listener.BeginAccept(...)
  6. Контрольная нить: stopListening() выходит, а слушатель продолжает работать!
  7. Поток слушателя: 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 является фоновым потоком, тогда как обычный поток является приоритетным.

Другие вопросы по тегам