Monday, June 16, 2008

Releasing Unmanaged Resources

Any type allocating unmanaged resources, either directly or indirectly, has to provide a mechanism to release the resources. The simplest way of releasing an unmanaged resource is to release it in the Finalizer of the type owning the resource. This approach is perfectly fine for releasing resources that do not have limits on how many you can acquire and do not have a cost associated with keeping them acquired longer than absolutely necessary. Unfortunately, most unmanaged resources are not limitless and not free; rather they are scarce. Database connection is a perfect example of a scarce resource; you can open only a limited number them.
Finalizers are not very good at releasing scarce resources. They run too late. GC starts collection, and runs finalizers, when it detects memory allocation problems. It is not able to detect unmanaged resource allocation problems.
The solution is to provide clients of types that acquire scarce resources with a way to explicitly release the resources. This way the client can release the resources as soon as they are not needed anymore. For example:
try{
MessageQueue queue = new MessageQueue(path);queue.Send(message);
}
finally{
queue.Dispose(); // explicitly release queue handle
}

To formalize and organize the process of releasing scarce resources, we have defined a couple of design guidelines and the IDisposable interface.
public interface IDisposable {
public void Dispose();
}

This defines a basic interface that declares the Dispose method, which is designed to be a common cleanup routine. When you implement this interface on a type, you are advertising that instances of that type allocate scarce resources. Some scenarios require clients to explicitly release the resources and some don’t. When in doubt, play it safe and always release the resource by making sure Dispose is called after the object is no longer needed.
For example, common implementations of server applications instantiate a new resource in every request, use the resource, and then release it. Such implementations will result in the cycle of allocate->use->release being repeated many times during the lifetime of the application. If the resource is scarce, it is desirable for the cycle to be as short as possible to maximize the number of requests that the application can process per unit of time. Calling Dispose() immediately after the resource is no longer needed will result in maximum throughput of the application.
Also, some multi-user client applications using resources that are shared among users should dispose the resources as soon as possible. This will prevent problems in which some users are denied access to a resource only because another user’s resource is waiting in the finalization queue. Holding a database connection open for longer than needed is a good example of such a programming mistake.
Single user applications, which don’t share scarce resources with other applications, don’t require such urgency in disposing resources. For example, a command line tool that creates a MessageQueue, sends a message, and terminates doesn’t need to explicitly dispose the MessageQueue instance. Please note that if the same code were executed in a server request, it would certainly cause a throughput problem.

No comments: