Personally, I prefer to learn a system by working on it instead of studying it from the outside. Bugs, except the most trivial ones, are great for this because you have to walk through the system to sort out cause from effect and you can't help but learn as you do that. If you don't have bugs, you can get some mileage out of utilities you've always wanted but haven't had time to write (maybe you want your code to use a new function to generate a standard window title). The key there is not just doing the discrete work (the function), but the weaving it into existing code.
However, occasionally I'll run into a ? that I can't answer myself directly from the code I see (a good example would be a message queue where I only have access to the producer or consumer). For that, I either need some external resource, whether it's someone to ask ?'s of, or documentation to read or even a fair amount of time to see the whole system working together.
Probably the least effective method I've run into is having someone try to walk me through a system. Very few people are able to explain a system from soup to nuts without taking their own knowledge base for granted. If you do decide to do this, the best method I've found is to treat the explanation as a series of problem/solution pairs. Keep reframing the discussion in terms of a conceptual problem, and then point the code that solves that problem. You can drill down as needed, and the constant reframing gives enough breadcrumbs that it's easy for an outsider to follow.