Click here to Skip to main content
12,077,911 members (48,807 online)
Click here to Skip to main content
Articles » Languages » C# » General » Downloads

Tagged as

Stats

11.8K views
325 downloads
26 bookmarked
Posted

Lazy Alternatives - LazyAndWeak and BackgroundLoader

, 1 Dec 2011 CPOL
This article will present two alternatives to Lazy.
LazyAlternatives.zip
LazyAlternatives
LazyAlternatives.suo
LazyAlternativesSample
Properties
Settings.settings
Pfz
Caching
Collections
DataTypes
DynamicObjects
Internal
Extensions
Factoring
Pfz.csproj.user
Pfz.Phone.csproj.user
Pfz.ruleset
Pfz.Silverlight.csproj.user
Pfz.snk
Pfz.suo
PhoneSpecific
Properties
Remoting
Instructions
Internal
Serializers
Udp
Serialization
BinaryBuiltIn
Threading
Contexts
Disposers
Unsafe
using System;
using System.Reflection;
using System.Threading;
using System.Security.Permissions;

namespace Pfz.Threading
{
    /// <summary>
    /// Class that allows thread-aborts to be done in a relativelly safe manner.
    /// </summary>
    public static class SafeAbort
    {
        /// <summary>
        /// Aborts a thread only if it is safe to do so, taking the abort mode into account (which may
        /// range from only guaranteeing that IDisposable objects will be fully constructed, up to 
        /// guaranteeing all "using" blocks to work and even doing some user validations).
        /// Returns if the Thread.Abort() was called or not.
        /// </summary>
        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 618
                    thread.Suspend();
                #pragma warning restore 618
            }
            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();

                    // Static constructors naturally delay the normal abort, so if we are inside one, we can abort.
                    // I test by name, as I didn't find an way to test more directly, and I discovered that 
                    // static constructors return IsConstructor as false.
                    if (method.IsStatic && method.IsSpecialName && method.Name == ".cctor")
                    {
                        canAbort = true;
                        break;
                    }

                    // 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(thread, stack, frames);
                                        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 618
                    thread.Resume();
                #pragma warning restore 618
            }
        }

        /// <summary>
        /// Aborts a thread, trying to use the safest abort mode, until the unsafest one.
        /// The number of retries is also the expected number of milliseconds trying to abort.
        /// </summary>
        public static bool Abort(Thread thread, int triesWithAllValidations, int triesIgnoringUserValidations, int triesAllowingUsingsToFail, bool finalizeWithNormalAbort = false, object stateInfo = null)
        {
            if (thread == null)
                throw new ArgumentNullException("thread");

            for (int i = 0; i < triesWithAllValidations; i++)
            {
                if (AbortIfSafe(thread, SafeAbortMode.RunAllValidations, stateInfo))
                    return true;

                Thread.Sleep(1);
            }

            for (int i = 0; i < triesIgnoringUserValidations; i++)
            {
                if (AbortIfSafe(thread, SafeAbortMode.IgnoreUserValidations, stateInfo))
                    return true;

                Thread.Sleep(1);
            }

            for (int i = 0; i < triesAllowingUsingsToFail; i++)
            {
                if (AbortIfSafe(thread, SafeAbortMode.AllowUsingsToFail, stateInfo))
                    return true;

                Thread.Sleep(1);
            }

            if (finalizeWithNormalAbort)
            {
                thread.Abort(stateInfo);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Event invoked by AbortIfSafe if user validations are valid and when it is unsure if the thread
        /// is in a safe situation or not.
        /// </summary>
        public static event EventHandler<SafeAbortEventArgs> Validating;
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Paulo Zemek
Engineer Microsoft Corporation
United States United States
I started to program computers when I was 11 years old, as a hobbist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do they work easier, faster and with less errors.

Now I just started working as a Senior Software Engineer at Microsoft.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (now I work at Microsoft so I can't be a Microsoft MVP anymore)

You may also be interested in...

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.160212.1 | Last Updated 1 Dec 2011
Article Copyright 2011 by Paulo Zemek
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid