public void Cancel()
{
bool callOnFail = false;
lock (_lockObject)
{
switch (_state)
{
case State.NotStarted:
// Cancel was called before the Dns query was started. The dns query won't be started
// and the connection attempt will fail synchronously after the state change to DnsQuery.
// All we need to do here is close all the sockets.
callOnFail = true;
break;
case State.DnsQuery:
// Cancel was called after the Dns query was started, but before it finished. We can't
// actually cancel the Dns query, but we'll fake it by failing the connect attempt asynchronously
// from here, and silently dropping the connection attempt when the Dns query finishes.
Task.Factory.StartNew(
s => CallAsyncFail(s),
null,
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default);
callOnFail = true;
break;
case State.ConnectAttempt:
case State.UserConnectAttempt:
// Cancel was called after the Dns query completed, but before we had a connection result to give
// to the user. Closing the sockets will cause any in-progress ConnectAsync call to fail immediately
// with OperationAborted, and will cause ObjectDisposedException from any new calls to ConnectAsync
// (which will be translated to OperationAborted by AttemptConnection).
callOnFail = true;
break;
case State.Completed:
// Cancel was called after we locked in a result to give to the user. Ignore it and give the user
// the real completion.
break;
default:
NetEventSource.Fail(this, "Unexpected object state");
break;
}
_state = State.Canceled;
}
// Call this outside the lock because Socket.Close may block
if (callOnFail)
{
OnFailOuter(true);
}
}