I got this question via email:
I am trying to determine why an application (I didn’t initially design it) that uses MEF and PRISM is slow (example: it takes more than 1 minute to login). When I looked at the code I noticed the following:
1. Many of the classes that are decorated with the [Export] attribute and have the [ImportingConstructor] attribute on their constructors do significant work in their constructors (large queries that take several seconds to finish, calls to services, etc.).
2. MEF instantiate the above classes when the application starts.
When MEF instantiates these classes at start time, shouldn’t the constructors of these classes (i.e., the ones with the [ImportingConstructor] attribute) be as simple as possible? That is, avoid doing any lengthy operations so as to minimize the time it takes for MEF to finish instantiating all the classes that participate in composition?
Of course you don’t want your application doing all this work at startup and making your user wait! It’s great that you tracked the performance problem to something that is fixable.
There are a couple of parts to the answer.
Simplifying importing constructors
Importing constructors should be as simple as possible. That’s not just for performance. When things go bad in the creation of classes in MEF (or pretty much any DI tool), sorting out the problem can be a nightmare. Put more simply: do what you can to ease debugging object construction, and that means simplifying constructors.
In most cases, the easiest way is to create properties for the result of each query and have the query performed the first time it’s needed- a simple check against null followed by the query only when needed. Depending on the complexity of your application and the number of times this occurs, this is an easy or a rather tedious refactoring.
The second point is that there is no inherent reason for MEF to be instantiating everything at startup. MEF instantiates classes when they are needed and something is requesting an instance of this class. These chains can become rather long and complicated when an instance of one class needs an instance of another, and another, and another. This is a general problem for DI containers.
I don’t know if this is happening inside PRISM or in code that you might have more control over, but this can happen in UI code where one screen or menu requires a reference to another, and another, and another.
It is possible that when you solve the problem with the importing constructors, you will still have a performance problem because of excessive linking – too many instances created. It will still be a design problem, and still not a MEF problem.
If this is deep in PRISM, I don’t know what to say, I don’t know PRISM.
If you encounter this in code you control, look at the Lazy class. Yep, there’s a Lazy class in .NET, I love that. While it’s slightly more difficult to use, it instantiates a simple lightweight object that has the capacity to create the actual instance at the time you first need it. This allows you to delay creation until you actually need it. Once the underlying instance is created, all requests for the value of the Lazy returns that first instance – no further instances are created.
MEF has other features for controlling lifetime, but they are more commonly used when you need additional instances in specific scenarios.
Other possible problems
A minute is a really long time in our world. More than a minute to login is a really, really long time.
It’s possible that you have additional issues. I suggest that you also check for pooling issues if you’re directly accessing a database (such as creating excess connections). Several seconds for a query against a local database is fairly long and you may also have indexing issues.
If the queries really are that long and when you are making calls to services, your users may still feel pain, even if it happens a different point in the application. You may need to shift to asynchronous calls for some of this work. I know async still feels like a high dive with no water in the pool, but it’s where we’re slowly going.
If this is a WPF application, or another application that loads a lot of assemblies (DLLs) and that has a predictable start-up sequence, explore multi-core JIT. If you have access to Pluralsight, you can find a discussion here in the clip titled “Multi-core background JIT.” It’s almost unknown, but you can get a significant boost in your assembly load time by adding a single line of code. If you don’t have other problems, this can improve overall startup times by about 20%.
It’s almost comical, but that one line of code is the name of a file. No matter how much we want this feature to just work, .NET can’t do something to the target machine without permission. You have to tell it where to put the information it uses to orchestrate JIT. That is, unless you’re using ASP.NET. Since ASP.NET is in the habit of putting files on its server, multi-core background JIT is automatically enabled for ASP.NET.
Your problem isn’t MEF, but…
I appreciate the question because you aren’t blaming MEF for other design problems. However, in our current server focused world, I want to point out that classic MEF is a little heavy. It is an amazingly powerful tool, but on a server with throughput issues and on a low power device it may not be the best tool.
Microsoft has also provided a different lightweight version of MEF. It’s had at least three names, and is currently on NuGet as MEF 2.
If you read my blog, you may have gathered between the lines that I think the core CLR teams are doing absolutely amazing, astounding things and that their communication with the community, their ability to toot their own horn, sucks. You didn’t know one line of code could improve your app load 20%, right? One of the ways that communication sucks is a lack of clarity on the future of MEF. It is inconceivable to me that as a tool as important as MEF could not have a long and bright future in both the rich and lightweight versions. But I wish the silence around MEF was not so loud.
I hope you’ll add to the comments what you tried that worked and didn’t work!