为了账号安全,请及时绑定邮箱和手机立即绑定

循环不会因 Thread 和 CancellationToken 而停止

循环不会因 Thread 和 CancellationToken 而停止

C#
拉风的咖菲猫 2023-08-13 13:55:41
我正在使用异步回调的 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();        }    }
查看完整描述

1 回答

?
慕哥9229398

TA贡献1877条经验 获得超6个赞

您的代码存在竞争条件,可能会导致死锁(有时)。让我们将线程命名为“监听器”(运行的线程_StartListening)和“控制”(运行的线程StopListening):

  1. 侦听器线程:if (cancelToken.IsCancellationRequested)-> false

  2. 控制线程:cancelSource.Cancel()

  3. 控制线程:allDone.Set()

  4. 侦听器线程:allDone.Reset()-> 意外重置停止请求!

  5. 监听线程:listener.BeginAccept(...)

  6. 控制线程:stopListening()退出,而监听器继续工作!

  7. 侦听器线程: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退出。


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


查看完整回答
反对 回复 2023-08-13
  • 1 回答
  • 0 关注
  • 89 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信