循环不会因 Thread 和 CancellationToken 而停止
Loop won't stop with Thread and CancellationToken
我正在处理一个使用异步回调的 Windows 套接字应用程序。如果我用Thread启动_StartListening
,当我调用StopListening
时,循环仍然停在allDone.WaitOne()
。但是任务版本就可以了。
有什么区别?
我的代码是 this
的修改版本
带有 ManualResetEvent
的原始版本具有 提到的竞争条件。我将其更改为 SemaphoreSlim
但问题仍然存在。
我在调试模式下试过,即使我没有启动客户端,在我调用 StopListening
之后似乎断点从未在 if (cancelToken.IsCancellationRequested)
命中。
对不起。我发现我不小心启动了两个套接字服务器。就是这个问题。
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();
}
}
您的代码存在可能导致死锁(有时)的竞争条件。我们将线程命名为 "listener"(运行 _StartListening
的线程)和 "control"(运行 StopListening
的线程):
- 侦听器线程:
if (cancelToken.IsCancellationRequested)
-> false
- 控制线程:
cancelSource.Cancel()
- 控制线程:
allDone.Set()
- 侦听器线程:
allDone.Reset()
-> 不小心重置了停止请求!
- 监听线程:
listener.BeginAccept(...)
- 控制线程:
stopListening()
退出,而侦听器继续工作!
- 侦听器线程:
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
不会 return 直到有新的客户端连接。停止监听器时,需要关闭socket(listener.Close()
)强制BeginAccept
退出。
Thread/Task 行为的差异真的很奇怪,它可能源于任务线程是后台线程,而常规线程是前台线程。
我正在处理一个使用异步回调的 Windows 套接字应用程序。如果我用Thread启动_StartListening
,当我调用StopListening
时,循环仍然停在allDone.WaitOne()
。但是任务版本就可以了。
有什么区别?
我的代码是 this
的修改版本带有 ManualResetEvent
的原始版本具有 SemaphoreSlim
但问题仍然存在。
我在调试模式下试过,即使我没有启动客户端,在我调用 StopListening
之后似乎断点从未在 if (cancelToken.IsCancellationRequested)
命中。
对不起。我发现我不小心启动了两个套接字服务器。就是这个问题。
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();
}
}
您的代码存在可能导致死锁(有时)的竞争条件。我们将线程命名为 "listener"(运行 _StartListening
的线程)和 "control"(运行 StopListening
的线程):
- 侦听器线程:
if (cancelToken.IsCancellationRequested)
-> false - 控制线程:
cancelSource.Cancel()
- 控制线程:
allDone.Set()
- 侦听器线程:
allDone.Reset()
-> 不小心重置了停止请求! - 监听线程:
listener.BeginAccept(...)
- 控制线程:
stopListening()
退出,而侦听器继续工作! - 侦听器线程:
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
不会 return 直到有新的客户端连接。停止监听器时,需要关闭socket(listener.Close()
)强制BeginAccept
退出。
Thread/Task 行为的差异真的很奇怪,它可能源于任务线程是后台线程,而常规线程是前台线程。