public static bool AbortIfSafe(Thread thread, SafeAbortMode mode = SafeAbortMode.RunAllValidations, object stateInfo = null) { if (thread == null) throw new ArgumentNullException("thread"); // If for some reason we are trying to abort our actual thread, we simple call Thread.Abort() directly. if (thread == Thread.CurrentThread) thread.Abort(stateInfo); // We check the state of the thread, ignoring if the thread also has Suspended or SuspendRequested in its state. switch (thread.ThreadState & ~(ThreadState.Suspended | ThreadState.SuspendRequested)) { case ThreadState.Running: case ThreadState.Background: case ThreadState.WaitSleepJoin: break; case ThreadState.Stopped: case ThreadState.StopRequested: case ThreadState.AbortRequested: case ThreadState.Aborted: return true; default: throw new ThreadStateException("The thread is in an invalid state to be aborted."); } try { #pragma warning disable thread.Suspend(); #pragma warning restore } catch (ThreadStateException) { switch (thread.ThreadState & ~(ThreadState.Suspended | ThreadState.SuspendRequested)) { case ThreadState.Aborted: case ThreadState.Stopped: // The thread terminated just when we are trying to Abort it. So, we will return true to tell that the "Abort" succeeded. return true; } // we couldn't discover why Suspend threw an exception, so we rethrow it. throw; } // We asked the thread to suspend, but the thread may take some time to be really suspended, so we will wait until it is no more in "SuspendRequested" state. while (thread.ThreadState == ThreadState.SuspendRequested) Thread.Sleep(1); // The Thread ended just when we asked it to suspend. So, for us, the Abort succeeded. if ((thread.ThreadState & (ThreadState.Stopped | ThreadState.Aborted)) != ThreadState.Running) return true; try { var stack = new System.Diagnostics.StackTrace(thread, false); var frames = stack.GetFrames(); // If we try to Abort the thread when it is starting (really soon), it will not have any frames. Calling an abort here caused some dead-locks for me, // so I consider that a Thread with no frames is a thread that can't be aborted. if (frames == null) return false; bool? canAbort = null; // In the for block, we start from the oldest frame to the newest one. // In fact, we check this: If the method returns IDisposable, then we can't abort. If this is not the case, then if we are inside a catch or finally // block, we can abort, as such blocks delay the normal abort and, so, even if an internal call is inside a constructor of an IDisposable, we // will not cause any problem calling abort. That's the reason to start with the oldest frame to the newest one. // And finally, if we are not in a problematic frame or in a guaranteed frame, we check if the method has try blocks. If it has, we consider // we can't abort. Note that if you do a try/catch and then an infinite loop, this check will consider the method to be inabortable. for (int i = frames.Length - 1; i >= 0; i--) { var frame = frames[i]; var method = frame.GetMethod(); // if we are inside a constructor of an IDisposable object or inside a function that returns one, we can't abort. if (method.IsConstructor) { ConstructorInfo constructorInfo = (ConstructorInfo)method; if (typeof(IDisposable).IsAssignableFrom(constructorInfo.DeclaringType)) { canAbort = false; break; } } else { MethodInfo methodInfo = (MethodInfo)method; if (typeof(IDisposable).IsAssignableFrom(methodInfo.ReturnType)) { canAbort = false; break; } } // Checks if the method, its class or its assembly has HostProtectionAttributes with MayLeakOnAbort. // If that's the case, then we can't abort. var attributes = (HostProtectionAttribute[])method.GetCustomAttributes(typeof(HostProtectionAttribute), false); foreach (var attribute in attributes) { if (attribute.MayLeakOnAbort) { canAbort = false; break; } } attributes = (HostProtectionAttribute[])method.DeclaringType.GetCustomAttributes(typeof(HostProtectionAttribute), false); foreach (var attribute in attributes) { if (attribute.MayLeakOnAbort) { canAbort = false; break; } } attributes = (HostProtectionAttribute[])method.DeclaringType.Assembly.GetCustomAttributes(typeof(HostProtectionAttribute), false); foreach (var attribute in attributes) { if (attribute.MayLeakOnAbort) { canAbort = false; break; } } var body = method.GetMethodBody(); if (body == null) continue; // if we were inside a finally or catch, we can abort, as the normal Thread.Abort() will be naturally delayed. int offset = frame.GetILOffset(); foreach (var handler in body.ExceptionHandlingClauses) { int handlerOffset = handler.HandlerOffset; int handlerEnd = handlerOffset + handler.HandlerLength; if (offset >= handlerOffset && offset < handlerEnd) { canAbort = true; break; } if (canAbort.GetValueOrDefault()) break; } } if (canAbort == null) { if (mode == SafeAbortMode.AllowUsingsToFail) canAbort = true; else { // we are inside an unsure situation. So, we will try to check the method. var frame = frames[0]; var method = frame.GetMethod(); var body = method.GetMethodBody(); if (body != null) { var handlingClauses = body.ExceptionHandlingClauses; if (handlingClauses.Count == 0) { canAbort = true; // Ok, by our tests we can abort. But, if the mode is RunAllValidations and there are user-validations, we must // run them. if (mode == SafeAbortMode.RunAllValidations) { var handler = Validating; if (handler != null) { SafeAbortEventArgs args = new SafeAbortEventArgs(); handler(null, args); // The args by default has its CanAbort set to true. But, if any handler changed it, we will not be able to abort. canAbort = args.CanAbort; } } } } } } if (canAbort.GetValueOrDefault()) { try { // We need to call abort while the thread is suspended, that works, but causes an exception, so we ignore it. thread.Abort(stateInfo); } catch { } return true; } return false; } finally { #pragma warning disable thread.Resume(); #pragma warning restore } }
public static bool AbortIfSafe(Thread thread, SafeAbortMode mode = SafeAbortMode.RunAllValidations, object stateInfo = null) { if (thread == null) { throw new ArgumentNullException("thread"); } // If for some reason we are trying to abort our actual thread, we simple call Thread.Abort() directly. if (thread == Thread.CurrentThread) { thread.Abort(stateInfo); } // We check the state of the thread, ignoring if the thread also has Suspended or SuspendRequested in its state. switch (thread.ThreadState & ~(ThreadState.Suspended | ThreadState.SuspendRequested)) { case ThreadState.Running: case ThreadState.Background: case ThreadState.WaitSleepJoin: break; case ThreadState.Stopped: case ThreadState.StopRequested: case ThreadState.AbortRequested: case ThreadState.Aborted: return(true); default: throw new ThreadStateException("The thread is in an invalid state to be aborted."); } try { #pragma warning disable thread.Suspend(); #pragma warning restore } catch (ThreadStateException) { switch (thread.ThreadState & ~(ThreadState.Suspended | ThreadState.SuspendRequested)) { case ThreadState.Aborted: case ThreadState.Stopped: // The thread terminated just when we are trying to Abort it. So, we will return true to tell that the "Abort" succeeded. return(true); } // we couldn't discover why Suspend threw an exception, so we rethrow it. throw; } // We asked the thread to suspend, but the thread may take some time to be really suspended, so we will wait until it is no more in "SuspendRequested" state. while (thread.ThreadState == ThreadState.SuspendRequested) { Thread.Sleep(1); } // The Thread ended just when we asked it to suspend. So, for us, the Abort succeeded. if ((thread.ThreadState & (ThreadState.Stopped | ThreadState.Aborted)) != ThreadState.Running) { return(true); } try { #pragma warning disable var stack = new System.Diagnostics.StackTrace(thread, false); #pragma warning restore var frames = stack.GetFrames(); // If we try to Abort the thread when it is starting (really soon), it will not have any frames. Calling an abort here caused some dead-locks for me, // so I consider that a Thread with no frames is a thread that can't be aborted. if (frames == null) { return(false); } bool?canAbort = null; // In the for block, we start from the oldest frame to the newest one. // In fact, we check this: If the method returns IDisposable, then we can't abort. If this is not the case, then if we are inside a catch or finally // block, we can abort, as such blocks delay the normal abort and, so, even if an internal call is inside a constructor of an IDisposable, we // will not cause any problem calling abort. That's the reason to start with the oldest frame to the newest one. // And finally, if we are not in a problematic frame or in a guaranteed frame, we check if the method has try blocks. If it has, we consider // we can't abort. Note that if you do a try/catch and then an infinite loop, this check will consider the method to be inabortable. for (int i = frames.Length - 1; i >= 0; i--) { var frame = frames[i]; var method = frame.GetMethod(); // if we are inside a constructor of an IDisposable object or inside a function that returns one, we can't abort. if (method.IsConstructor) { ConstructorInfo constructorInfo = (ConstructorInfo)method; if (typeof(IDisposable).IsAssignableFrom(constructorInfo.DeclaringType)) { canAbort = false; break; } } else { MethodInfo methodInfo = (MethodInfo)method; if (typeof(IDisposable).IsAssignableFrom(methodInfo.ReturnType)) { canAbort = false; break; } } // Checks if the method, its class or its assembly has HostProtectionAttributes with MayLeakOnAbort. // If that's the case, then we can't abort. var attributes = (HostProtectionAttribute[])method.GetCustomAttributes(typeof(HostProtectionAttribute), false); foreach (var attribute in attributes) { if (attribute.MayLeakOnAbort) { canAbort = false; break; } } attributes = (HostProtectionAttribute[])method.DeclaringType.GetCustomAttributes(typeof(HostProtectionAttribute), false); foreach (var attribute in attributes) { if (attribute.MayLeakOnAbort) { canAbort = false; break; } } attributes = (HostProtectionAttribute[])method.DeclaringType.Assembly.GetCustomAttributes(typeof(HostProtectionAttribute), false); foreach (var attribute in attributes) { if (attribute.MayLeakOnAbort) { canAbort = false; break; } } var body = method.GetMethodBody(); if (body == null) { continue; } // if we were inside a finally or catch, we can abort, as the normal Thread.Abort() will be naturally delayed. int offset = frame.GetILOffset(); foreach (var handler in body.ExceptionHandlingClauses) { int handlerOffset = handler.HandlerOffset; int handlerEnd = handlerOffset + handler.HandlerLength; if (offset >= handlerOffset && offset < handlerEnd) { canAbort = true; break; } if (canAbort.GetValueOrDefault()) { break; } } } if (canAbort == null) { if (mode == SafeAbortMode.AllowUsingsToFail) { canAbort = true; } else { // we are inside an unsure situation. So, we will try to check the method. var frame = frames[0]; var method = frame.GetMethod(); var body = method.GetMethodBody(); if (body != null) { var handlingClauses = body.ExceptionHandlingClauses; if (handlingClauses.Count == 0) { canAbort = true; // Ok, by our tests we can abort. But, if the mode is RunAllValidations and there are user-validations, we must // run them. if (mode == SafeAbortMode.RunAllValidations) { var handler = Validating; if (handler != null) { SafeAbortEventArgs args = new SafeAbortEventArgs(); handler(null, args); // The args by default has its CanAbort set to true. But, if any handler changed it, we will not be able to abort. canAbort = args.CanAbort; } } } } } } if (canAbort.GetValueOrDefault()) { try { // We need to call abort while the thread is suspended, that works, but causes an exception, so we ignore it. thread.Abort(stateInfo); } catch { } return(true); } return(false); } finally { #pragma warning disable thread.Resume(); #pragma warning restore } }