我正在使用异步回调处理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();
}
}
答案 0 :(得分:0)
您的代码具有竞争条件,可能导致死锁(有时)。我们将线程命名为“侦听器”(运行_StartListening
的线程)和“控制”(运行StopListening
的线程):
if (cancelToken.IsCancellationRequested)
->假cancelSource.Cancel()
allDone.Set()
allDone.Reset()
->意外重置了停止请求!listener.BeginAccept(...)
stopListening()
退出,而侦听器继续工作!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()
退出。
线程/任务行为的差异确实很奇怪,它可能源自任务线程是后台线程,而常规线程是前台线程。