循环不会因Thread和CancellationToken而停止

时间:2019-08-06 03:28:48

标签: c# multithreading sockets windows-socket-api

我正在使用异步回调处理Windows套接字应用程序。如果我使用线程启动_StartListening,则当我调用StopListening时,循环仍在allDone.WaitOne()处停止。但是Task版本将可以。

有什么区别?

我的代码是this的修改版本

带有ManualResetEvent的原始版本具有felix-b提到的竞争条件。我将其更改为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 个答案:

答案 0 :(得分:0)

您的代码具有竞争条件,可能导致死锁(有时)。我们将线程命名为“侦听器”(运行_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()进行,而{{1 }}应该做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()退出。

线程/任务行为的差异确实很奇怪,它可能源自任务线程是后台线程,而常规线程是前台线程。