0

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

有什么不同?

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

原始版本具有felix-bManualResetEvent提到的竞争条件。我把它改成了,但问题仍然存在。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();
        }
    }
4

1 回答 1

1

您的代码有可能导致死锁的竞争条件(有时)。让我们将线程命名为“listener”(运行的_StartListening)和“control”(运行的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()在它出于任何原因退出之前做,而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退出。

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

于 2019-08-06T04:09:16.307 回答