New to Threading/Tasks and async processing...
I have a process that attempts to acquire a file, however, if the file is pwd protected, the call to GetDocument
never returns, and hangs the service.
In all of the "sample" code and tutorials I've looked at, the threaded process seems to be multiple lines wrapped within a loop of some sort, thus allowing for the ability to cancel within a while clause or whatever.
Would a Task be more suited due to a single line of code trying to be executed?
Any other suggestions?
public class ServerClass
{
public static PageData pageData;
public static ImageDataProvider idp;
public static Rendition rend;
public static void AcquireRendition(object obj)
{
CancellationToken ct = (CancellationToken)obj;
while ((!ct.IsCancellationRequested) || (pageData == null))
{
pageData = idp.GetDocument(rend); ////line failing to return
}
}
}
CodePudding user response:
Are you sure there isn't an API that allows you to pass a cancellation token? Are there alternative clients/libraries you could use? There is no generally safe way to stop a "hung" call. Even having a synchronous blocking I/O method is a very bad idea. Then you make it even worse by having the whole object as a static field - and accessed with no regard to sharing (is ImageDataProvider
thread-safe?).
If you really can't get a better API, you will probably have to separate the whole thing into a different process - that you can terminate.
Tasks definitely cannot be terminated (they rely entirely on cooperative cancellation) and Threads are very unsafe and unreliable with "rude" aborts (again, cooperative cancellation is vastly preferred). You have no idea what kind of corruption that can cause, and making code that can handle asynchronous exceptions reasonably well is pretty much impossible. You certainly can't expect it from a library that doesn't even provide an opportunity for cooperative cancellation.
CodePudding user response:
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
// 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.
}
}