https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.beginaccept?view=net-5.0
To cancel a pending call to the BeginAccept method, close the Socket.
When the Close method is called while an asynchronous operation is in
progress, the callback provided to the BeginAccept method is called. A
subsequent call to the EndAccept method will throw an
ObjectDisposedException to indicate that the operation has been
cancelled.
Here the TcpListner.cs decompiled.
[HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
public Task<TcpClient> AcceptTcpClientAsync()
{
return Task<TcpClient>.Factory.FromAsync(BeginAcceptTcpClient, EndAcceptTcpClient, null);
}
/// <summary>Asynchronously accepts an incoming connection attempt and creates a new <see cref="T:System.Net.Sockets.TcpClient" /> to handle remote host communication.</summary>
/// <returns>A <see cref="T:System.Net.Sockets.TcpClient" />.</returns>
/// <param name="asyncResult">An <see cref="T:System.IAsyncResult" /> returned by a call to the <see cref="M:System.Net.Sockets.TcpListener.BeginAcceptTcpClient(System.AsyncCallback,System.Object)" /> method.</param>
/// <PermissionSet>
/// <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence" />
/// <IPermission class="System.Diagnostics.PerformanceCounterPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// </PermissionSet>
public TcpClient EndAcceptTcpClient(IAsyncResult asyncResult)
{
if (Logging.On)
{
Logging.Enter(Logging.Sockets, this, "EndAcceptTcpClient", null);
}
if (asyncResult == null)
{
throw new ArgumentNullException("asyncResult");
}
LazyAsyncResult lazyResult = asyncResult as LazyAsyncResult;
Socket asyncSocket = (lazyResult == null) ? null : (lazyResult.AsyncObject as Socket);
if (asyncSocket == null)
{
throw new ArgumentException(SR.GetString("net_io_invalidasyncresult"), "asyncResult");
}
Socket socket = asyncSocket.EndAccept(asyncResult);
if (Logging.On)
{
Logging.Exit(Logging.Sockets, this, "EndAcceptTcpClient", socket);
}
return new TcpClient(socket);
}
/// <summary>Begins an asynchronous operation to accept an incoming connection attempt.</summary>
/// <returns>An <see cref="T:System.IAsyncResult" /> that references the asynchronous creation of the <see cref="T:System.Net.Sockets.TcpClient" />.</returns>
/// <param name="callback">An <see cref="T:System.AsyncCallback" /> delegate that references the method to invoke when the operation is complete.</param>
/// <param name="state">A user-defined object containing information about the accept operation. This object is passed to the <paramref name="callback" /> delegate when the operation is complete.</param>
/// <exception cref="T:System.Net.Sockets.SocketException">An error occurred while attempting to access the socket. See the Remarks section for more information. </exception>
/// <exception cref="T:System.ObjectDisposedException">The <see cref="T:System.Net.Sockets.Socket" /> has been closed. </exception>
/// <PermissionSet>
/// <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence" />
/// <IPermission class="System.Diagnostics.PerformanceCounterPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// </PermissionSet>
[HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
public IAsyncResult BeginAcceptTcpClient(AsyncCallback callback, object state)
{
if (Logging.On)
{
Logging.Enter(Logging.Sockets, this, "BeginAcceptTcpClient", null);
}
if (!m_Active)
{
throw new InvalidOperationException(SR.GetString("net_stopped"));
}
IAsyncResult result = m_ServerSocket.BeginAccept(callback, state);
if (Logging.On)
{
Logging.Exit(Logging.Sockets, this, "BeginAcceptTcpClient", null);
}
return result;
}
And Socket.cs decompiled.
/// <summary>Asynchronously accepts an incoming connection attempt and creates a new <see cref="T:System.Net.Sockets.Socket" /> to handle remote host communication.</summary>
/// <returns>A <see cref="T:System.Net.Sockets.Socket" /> to handle communication with the remote host.</returns>
/// <param name="asyncResult">An <see cref="T:System.IAsyncResult" /> that stores state information for this asynchronous operation as well as any user defined data. </param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="asyncResult" /> is null. </exception>
/// <exception cref="T:System.ArgumentException">
/// <paramref name="asyncResult" /> was not created by a call to <see cref="M:System.Net.Sockets.Socket.BeginAccept(System.AsyncCallback,System.Object)" />. </exception>
/// <exception cref="T:System.Net.Sockets.SocketException">An error occurred when attempting to access the socket. See the Remarks section for more information. </exception>
/// <exception cref="T:System.ObjectDisposedException">The <see cref="T:System.Net.Sockets.Socket" /> has been closed. </exception>
/// <exception cref="T:System.InvalidOperationException">
/// <see cref="M:System.Net.Sockets.Socket.EndAccept(System.IAsyncResult)" /> method was previously called. </exception>
/// <exception cref="T:System.NotSupportedException">Windows NT is required for this method. </exception>
/// <PermissionSet>
/// <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence" />
/// <IPermission class="System.Diagnostics.PerformanceCounterPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// </PermissionSet>
public Socket EndAccept(IAsyncResult asyncResult)
{
if (s_LoggingEnabled)
{
Logging.Enter(Logging.Sockets, this, "EndAccept", asyncResult);
}
if (CleanedUp)
{
throw new ObjectDisposedException(GetType().FullName);
}
byte[] buffer;
int bytesTransferred;
if (asyncResult != null && asyncResult is AcceptOverlappedAsyncResult)
{
return EndAccept(out buffer, out bytesTransferred, asyncResult);
}
if (asyncResult == null)
{
throw new ArgumentNullException("asyncResult");
}
AcceptAsyncResult castedAsyncResult = asyncResult as AcceptAsyncResult;
if (castedAsyncResult == null || castedAsyncResult.AsyncObject != this)
{
throw new ArgumentException(SR.GetString("net_io_invalidasyncresult"), "asyncResult");
}
if (castedAsyncResult.EndCalled)
{
throw new InvalidOperationException(SR.GetString("net_io_invalidendcall", "EndAccept"));
}
object result = castedAsyncResult.InternalWaitForCompletion();
castedAsyncResult.EndCalled = true;
Exception exception = result as Exception;
if (exception != null)
{
throw exception;
}
if (castedAsyncResult.ErrorCode != 0)
{
SocketException socketException = new SocketException(castedAsyncResult.ErrorCode);
UpdateStatusAfterSocketError(socketException);
if (s_LoggingEnabled)
{
Logging.Exception(Logging.Sockets, this, "EndAccept", socketException);
}
throw socketException;
}
Socket acceptedSocket = (Socket)result;
if (s_LoggingEnabled)
{
Logging.PrintInfo(Logging.Sockets, acceptedSocket, SR.GetString("net_log_socket_accepted", acceptedSocket.RemoteEndPoint, acceptedSocket.LocalEndPoint));
Logging.Exit(Logging.Sockets, this, "EndAccept", result);
}
return acceptedSocket;
}
It seems that AcceptTcpClientAsync() uses something like BeginAccept() and EndAccept() internally. In Socket.cs you can see if CleanedUp is true throw ObjectDisposedException, which means listening socket is closed.
So closing listening socket causes AcceptTcpClientAsync() throw ObjectDisposedException.
namespace TestTcpListenStop {
class Program {
static TcpListener listner;
static void Main(string[] args) {
for (int i = 0; i < 100; ++i) {
StartStopTest();
}
Console.ReadKey();
return;
}
static void StartStopTest() {
// start listner
listner = new TcpListener(IPAddress.Any, 17000);
listner.Start();
// start accept
Task tk = AcceptAsync();
// do other things
Task.Delay(1).Wait();
// close listen socket
listner.Stop();
tk.Wait();
return;
}
static async Task AcceptAsync() {
Console.WriteLine("Accepting client...");
TcpClient client;
while (true) {
try {
// Closing listen socket causes
// AcceptTcpClientAsync() throw ObjectDisposedException
client = await listner.AcceptTcpClientAsync().ConfigureAwait(false);
Console.WriteLine("A client has been accepted.");
}
catch (ObjectDisposedException) {
Console.WriteLine("This exception means listening socket closed.");
break;
}
// we just close.
client.Client.Shutdown(SocketShutdown.Both);
client.Close();
}
Console.WriteLine("AcceptAsync() terminated.");
}
}
}
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.wait?view=net-5.0
Canceling the cancellationToken cancellation token has no effect on the running task unless it has also been passed the cancellation token and is prepared to handle cancellation. Passing the cancellationToken object to this method simply allows the wait to be canceled.
And I think using cancellation token doesn't actually stop AcceptTcpClientAsync(). We just cancel waiting, not AcceptTcpClientAsync() because AcceptTcpClientAsync() doesn't receive cancellation token as a parameter. Only closing listening socket can cancel AcceptTcpClientAsync(). Please see the following from msdn.
public class Example {
public static void Main() {
CancellationTokenSource ts = new CancellationTokenSource();
Task t = Task.Run(() => {
Console.WriteLine("Calling Cancel...");
ts.Cancel();
Task.Delay(5000).Wait();
Console.WriteLine("Task ended delay...");
});
try {
Console.WriteLine("About to wait for the task to complete...");
t.Wait(ts.Token);
}
catch (OperationCanceledException e) {
Console.WriteLine("{0}: The wait has been canceled. Task status: {1:G}",
e.GetType().Name, t.Status);
Thread.Sleep(6000);
Console.WriteLine("After sleeping, the task status: {0:G}", t.Status);
}
ts.Dispose();
}
}
// The example displays output like the following:
// About to wait for the task to complete...
// Calling Cancel...
// OperationCanceledException: The wait has been canceled. Task status: Running
// Task ended delay...
// After sleeping, the task status: RanToCompletion