I'll try to explain it in layman terms, hopefully it is not very far from the actual technical workings of a render engine. If someone with true programming inside knowledge wants to barge in, please do correct me if I'm wrong
While rendering Blender, and most rendering software for that matter, divides your image up into small digestible chunks, like buckets samples, or whatever method is more adequate for the current engine.
These chunks are then run through some sort of scheduling agent that queues them and sends them to the available processing units, be it a GPU, CPU core, one of several networked computers, a Farm or whatever.
The scheduler then waits for the processing units to churn through that data, produce the image slice and return them as rendered chunks to send in a new one afterwards.
Until that chunk is returned Blender has no control over that processing unit, all it can do is waiting for the returned information.
If you cancel a render most of the time the only way to actually stop it is by not sending a new chunk, and even if a "cancel" signal can be sent, often the processing unit is busy and can't act on it until it finishes that current job anyway.
The time until the signal is interpreted or the current chunk is finished is the delay you experience between pressing Cancel and actually stopping.
Following Scott Milner line of thought, the larger a render tile or bucket is, the longer it takes to render, so theoretically the longer the potential delay to cancel. The same applies for more complex algorithms like Branched Path Tracing which require more processing power and memory, and thus take longer to process.
Have in mind that lowering bucket size may increase responsiveness but at the expense of total render time, negatively affecting your performance for the benefit of faster cancelling, so you should weight the pros and cons of faster render or faster cancelling.