Resource Management

Chia sẻ: Nghia Tuan | Ngày: | Loại File: PDF | Số trang:5

lượt xem

Resource Management

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Quản lý tài nguyên Đôi khi không lưu ý để phát hành một nguồn tài nguyên trong destructor một, một số nguồn lực chỉ là quá có giá trị và quá khan hiếm nằm xung quanh thoát cho độ dài tùy ý thời gian. Khan hiếm nguồn lực cần phải được phát hành

Chủ đề:

Nội dung Text: Resource Management

  1. Resource Management Sometimes it's inadvisable to release a resource in a destructor; some resources are just too valuable and too scarce to lie around unreleased for arbitrary lengths of time. Scarce resources need to be released, and they need to be released as soon as possible. In these situations, your only option is to release the resource yourself. A disposal method is a method that disposes of a resource. If a class has a disposal method, you can call it explicitly and thereby control when the resource is released. Disposal Methods An example of a class that implements a disposal method is the TextReader class from the System.IO namespace. This class provides mechanisms to read characters from a sequential stream of input. TextReader contains a virtual method called Close, which closes the stream. The StreamReader class (which reads characters from a stream, such as an open file) and the StringReader class (which reads characters from a string) both derive from TextReader, and both override the Close method. Here's an example that reads lines of text from a file by using the StreamReader class, and then displays them on the screen: TextReader reader = new StreamReader(filename); string line; while ((line = reader.ReadLine()) != null) { Console.WriteLine(line); } reader.Close(); The ReadLine method reads the next line of text from the stream into a string. The ReadLine method returns null if there is nothing left in the stream. It's important to call Close when you have finished with reader to release the file handle and associated resources. However, there is a problem with this example; it's not exception-safe. If the call to ReadLine (or WriteLine) throws an exception, the call to Close will not happen; it will be bypassed. If this happens often enough, you will run out of file handles and be unable to open any more files. Exception-Safe Disposal One way to ensure that a disposal method (such as Close) is always called, regardless of whether there is an exception, is to call the disposal method inside a finally block. Here's the previous example coded by using this technique:
  2. TextReader reader = new StreamReader(filename); try { string line; while ((line = reader.ReadLine()) != null) { Console.WriteLine(line); } } finally { reader.Close(); } Using a finally block like this works, but it has several drawbacks that make it a less than ideal solution: • It quickly gets unwieldy if you have to dispose of more than one resource (you end up with nested try and finally blocks). • In some cases, you might have to modify the code (for example, reorder the declaration of the resource reference, remember to initialize the reference to null, and remember to check that the reference isn't null in the finally block). • It fails to create an abstraction of the solution. This means the solution is hard to understand and you must repeat the code everywhere you need this functionality. • The reference to the resource remains in scope after the finally block. This means that you can accidentally try to use the resource after it has been released. The using statement is designed to solve all these problems. The using Statement The using statement provides a clean mechanism for controlling the lifetimes of resources. You can create an object, and this object will be destroyed when the using statement block finishes. IMPORTANT Do not confuse the using statement shown in this section with the using directive that brings a namespace into scope. It is unfortunate that the same keyword has two different meanings. The syntax for a using statement is as follows: using ( type variable = initialization ) embeddedStatement
  3. Here is the best way to ensure that your code always calls Close on a TextReader: using (TextReader reader = new StreamReader(filename)) { string line; while ((line = reader.ReadLine()) != null) { Console.WriteLine(line); } } This using statement is precisely equivalent to the following translation: { TextReader reader = new StreamReader(filename); try { string line; while ((line = reader.ReadLine()) != null) { Console.WriteLine(line); } } finally { if (reader != null) { ((IDisposable)reader).Dispose(); } } } NOTE Note the outer block scope. This arrangement means that the variable you declare in a using statement goes out of scope at the end of the embedded statement. The variable you declare in a using statement must be of a type that implements the IDisposable interface. The IDisposable interface lives in the System namespace and contains just one method called Dispose: namespace System { interface IDisposable {
  4. void Dispose(); } } It just so happens that the StreamReader class implements the IDisposable interface, and its Dispose method calls Close to close the stream. You can use a using statement as a clean, exception-safe, robust way to ensure that a resource is always automatically released. This solves all of the problems that existed in the manual try/finally solution. You now have a solution that: • Scales well if you need to dispose of multiple resources. • Doesn't distort the logic of the program code. • Abstracts away the problem and avoids repetition. • Is robust. You can't use the variable declared inside the using statement, (in this case, reader) after the using statement has ended because it's not in scope anymore—you'll get a compile-time error. Calling the Dispose Method from a Destructor When writing a class, should you write a destructor, or implement the IDisposable interface? A call to a destructor will happen but you just don't know when. On the other hand you know exactly when a call to the Dispose method happens, but you just can't be sure that it will actually happen, because it relies on the programmer remembering to write a using statement. However, it is possible to ensure that the Dispose method always runs by calling it from the destructor. This acts as a useful backup. You might forget to call the Dispose method, but at least you can be sure that it will be called, even if it's only when the program shuts down. Here's an example of how to do this: class Example : IDisposable { ... ~Example() { Dispose(); } public virtual void Dispose() { if (!this.disposed) { try { // release scarce resource here }
  5. finally { this.disposed = true; GC.SuppressFinalize(this); } } } public void SomeBehavior() // example method { checkIfDisposed(); ... } ... private void checkIfDisposed() { if (this.disposed) { throw new ObjectDisposedException("Example"); } } private Resource scarce; private bool disposed = false; } Notice the following: • The class implements IDisposable. • The destructor calls Dispose. • The Dispose method is public and can be called at any time. • The Dispose method can safely be called multiple times. The variable disposed indicates whether the method has aleady been run before. The scarce resource is released only the first time the method runs. • The Dispose method calls the static GC.SuppressFinalize method. This method stops the garbage collector from calling the destructor on this object, because the object has now been finalized. • All the regular methods of the class (such as SomeBehavior) check to see whether the object has already been disposed. If it has, they throw an exception.
Đồng bộ tài khoản