循环不会因 Thread 和 CancellationToken 而停止

我正在使用异步回调的 Windows 套接字应用程序。如果我使用 Thread 来启动_StartListening,当我调用时StopListening,循环仍然停止在allDone.WaitOne(). 但Task版本就可以了。

有什么不同?

我的代码是这个的修改版本

原始版本有felix-bManualResetEvent提到的竞争条件。我把它改成了,但问题仍然存在。SemaphoreSlim

我在调试模式下尝试过,即使我不启动客户端,if (cancelToken.IsCancellationRequested)在我调用后似乎也不会遇到断点。StopListening

对不起。我发现我不小心启动了两个socket服务器。那就是问题所在。

 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();

        }

    }


拉风的咖菲猫
浏览 90回答 1
1回答

慕哥9229398

您的代码存在竞争条件,可能会导致死锁(有时)。让我们将线程命名为“监听器”(运行的线程_StartListening)和“控制”(运行的线程StopListening):侦听器线程:if (cancelToken.IsCancellationRequested)-> false控制线程:cancelSource.Cancel()控制线程:allDone.Set()侦听器线程:allDone.Reset()-> 意外重置停止请求!监听线程:listener.BeginAccept(...)控制线程:stopListening()退出,而监听器继续工作!侦听器线程:allDone.WaitOne()-> 死锁!没有人会这么做allDone.Set()。问题在于如何使用该allDone事件,应该是相反的:应该在它因任何原因退出之前_StartListening执行,而应该执行:allDone.Set()StopListeningallDone.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直到有新的客户端连接才会返回。停止监听时,需要关闭socket( listener.Close())才能强制BeginAccept退出。线程/任务行为的差异确实很奇怪,它可能源于任务线程是后台线程,而常规线程是前台线程。
打开App,查看更多内容
随时随地看视频慕课网APP