The issue I see with this block of code is that it does not protect itself from itself… Whenever you get into the realm of locking resources when dealing with threads it is wise to verify that u can indeed lock the resource.. Example let say somehow you got this code to execute twice, how does it know that you have already locked or unlock the resource? Well the answer based on this code is it does not, it has no clue as to whether user is currently being used on another thread or not, which will cause some nasty error that will make you smash ahead against the wall. So what’s the answer to this issue? Well once again .net has done the work for you, in what they call an Interlocked, the easy way of looking at this is it compares values and depending on the returned value will indicate whether there is a lock currently on the method or not.  So how would this work into your example is as following:

private static int currentlyinuse = 0;

private void cacheUser(User user)
{
   if (0 == Interlocked.Exchange(ref currentlyinuse, 1))
   {
      userLock.AcquireReaderLock(Timeout.Infinite);
      try
      {
        if (!_users.TryGetValue(user.Email, out user))
        {
           userLock.UpgradeToWriterLock(Timeout.Infinite);
            _users.Add(user.Email, user);
        }
      }
      finally
      {
          userLock.ReleaseLock();
          Interlocked.Exchange(ref currentlyinuse, 0);
       }
   }
}