You could try interrupting the potentially stuck thread with the Thread.Interrupt method. Below is a helper method RunInterruptible that observes a CancellationToken, interrupts the current thread in case the token is canceled, and propagates an OperationCanceledException. It has identical signature with the new API ControlledExecution.Run (.NET 7, source code):
public static void RunInterruptible(Action action,
CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException("action");
cancellationToken.ThrowIfCancellationRequested();
bool completedSuccessfully = false;
try
{
using (CancellationTokenRegistration _ = cancellationToken
.Register(arg => ((Thread)arg).Interrupt(), Thread.CurrentThread))
action();
completedSuccessfully = true;
Thread.Sleep(0); // Last chance to observe the effect of Interrupt
}
catch (ThreadInterruptedException)
{
if (completedSuccessfully) return;
cancellationToken.ThrowIfCancellationRequested();
throw;
}
}
Usage example:
RunInterruptible(() => pageData = idp.GetDocument(rend), ct);
In case the thread is not stuck in a waiting state, and instead it spins uncontrollably, the Thread.Interrupt will have no effect. In that case you could try using the RunAbortable method below. Please be sure that you are well aware of the implications of using the Thread.Abort method in a production environment, before adopting this drastic measure.
// .NET Framework only
[Obsolete("The RunAbortable method may prevent the execution of static" +
" constructors and the release of managed or unmanaged resources," +
" and may leave the application in an invalid state.")]
public static void RunAbortable(Action action,
CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException("action");
cancellationToken.ThrowIfCancellationRequested();
bool completedSuccessfully = false;
try
{
using (CancellationTokenRegistration _ = cancellationToken
.Register(arg => ((Thread)arg).Abort(), Thread.CurrentThread))
action();
completedSuccessfully = true;
Thread.Sleep(0); // Last chance to observe the effect of Abort
}
catch (ThreadAbortException)
{
if (completedSuccessfully)
{
Thread.ResetAbort();
return;
}
if (cancellationToken.IsCancellationRequested)
{
Thread.ResetAbort();
throw new OperationCanceledException(cancellationToken);
}
throw; // Redundant, the ThreadAbortException is rethrown anyway.
}
}