Parallel locking construct
public void Lock(string key, int timeout = 60, int tick = 100)
    {
        ...
        //timeout = ConvertTimeoutToTicks(timeout, tick);
        ...
        timeout = 600;



Looks like you didn't finish tidying up the code.




        //timeout is specified in seconds, tick in milliseconds



Why perpetuate the common mistake in the standard libraries? TimeSpan exists: use it! I wish Microsoft would [Obsolete] every method in .Net which takes an int or a long for a timespan and replace it with a version taking a TimeSpan.




        var counter = 0;
        do
        {
            ...
        } while (counter++ &lt; timeout);



Ok, now I'm even more confused. Is timeout supposed to be a timeout or a multiplier of tick?




            if (!Keys.Contains(key))
            {
                lock (Keys)
                {
                    if (!Keys.Contains(key))
                    {
                        Keys.Add(key);
                        return;
                    }
                }
            }



Just no. .Net's memory model fixes the subtle double-checked locking problem in Java, but only where the test is a simple boolean field. HashSet cannot guarantee that multi-threaded access will work correctly if one of the threads modifies it, so the unlocked test must be removed. However, I can offer an improvement which should compensate: use the return value of Add:

            lock (Keys)
            {
                if (Keys.Add(key))
                {
                    return;
                }
            }


Similarly, Unlock should wrap the current body in a lock statement.



What's the lifecycle of that ManualResetEvent? It's IDisposable, so you should take care of tidying it up yourself, probably with a using statement.