I'm working with a Windows socket application using async callbacks. If I use Thread to start _StartListening, when I call StopListening, the loop still stops at allDone.WaitOne(). But the Task version will be OK.
What's the difference?
My code is a modified version of this
The original version with ManualResetEvent has a race condition mentioned by felix-b. I changed it to SemaphoreSlim but the problem is still there.
I tried in debug mode and it seems that the break point never be hit at if (cancelToken.IsCancellationRequested) after I call StopListening even I don't start the client.
Sorry. I found that I accidentally started two socket servers. That's the problem.
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();
}
}
Your code has a race condition that can lead to deadlock (sometimes). Let's name the threads "listener" (one that runs
_StartListening) and "control" (one that runsStopListening):if (cancelToken.IsCancellationRequested)-> falsecancelSource.Cancel()allDone.Set()allDone.Reset()-> accidentally resets the stop request!listener.BeginAccept(...)stopListening()exits, while the listener continues to work!allDone.WaitOne()-> deadlock! no one will doallDone.Set().The problem is in how you use the
allDoneevent, it should be the other way around:_StartListeningshould doallDone.Set()just before it exits for whatever reason, whereasStopListeningshould doallDone.WaitOne():UPDATE
It worth noting that
listener.BeginAcceptwon't return until there is a new client connection. When stopping the listener, it is necessary to close the socket (listener.Close()) to forceBeginAcceptto exit.The difference in Thread/Task behavior is really weird, it probably can originate from the Task thread being a background thread, while the regular thread being a foreground one.