Um exemplo:
create proc [dbo].[usp_SlowProc]
as
begin try
set xact_abort on; -- Ensures rollback on some errors that bypass the try/catch mechanism
raiserror('Waiting...', 0, 1) with nowait
waitfor delay '00:00:10'
raiserror('Completed successfully.', 0, 1) with nowait
end try
begin catch
raiserror('Caught It.', 0, 1) with nowait
-- Rollback, log the error.
end catch
Em seguida, execute-o no SSMS/Toad:
exec dbo.usp_SlowProc;
e, em seguida, cancele-o (usando o SSMS/Toad) antes de concluí-lo. O bloco catch não é executado.
Assumindo a partir deste link que um cancelamento de usuário é o mesmo que o .NET faz em um tempo limite do cliente (o resultado é o mesmo).
AFAIK, não há nenhuma maneira no TSQL de lidar com isso além da maneira como você está atualmente com XACT_ABORT. Não há estruturas que o TSQL conheça (ou mesmo se preocupe), pois tudo o que ele lida vive fora do TSQL.
O que realmente está acontecendo é chamado de evento "Atenção" que pode variar de um fechamento de conexão a um cancelamento real (por exemplo, o método sqlcommand.cancel()). Como isso fica fora da consulta real, ele apenas informa ao SQL Server que algo aconteceu e que precisa limpar algumas coisas. A consulta real não tem ideia do que está acontecendo.
Try/Catch é para erros de execução de consulta, como mergulhar em 0, mas não para qualquer outro tipo de erro. Por exemplo, erros de compilação/análise não são capturados porque o try/catch nunca foi executado. Como os sinais de atenção não fazem parte da execução da consulta (novamente, está fora desse escopo), ela não sabe nada sobre isso e, portanto, não é chamada porque nenhuma exceção de execução ocorreu (de acordo com a própria consulta).