Quan điểm của Vứt bỏ là giải phóng tài nguyên không được quản lý. Nó cần phải được thực hiện tại một số điểm, nếu không chúng sẽ không bao giờ được làm sạch. Trình thu gom rác không biết cách gọi DeleteHandle()
một biến loại IntPtr
, nó không biết có cần gọi hay không DeleteHandle()
.
Lưu ý : Tài nguyên không được quản lý là gì? Nếu bạn tìm thấy nó trong Microsoft .NET Framework: nó được quản lý. Nếu bạn tự chọc vào MSDN, nó không được quản lý. Bất cứ điều gì bạn đã sử dụng P / Gọi các cuộc gọi để ra khỏi thế giới thoải mái của mọi thứ có sẵn trong .NET Framework đều không được quản lý - và giờ bạn có trách nhiệm dọn dẹp nó.
Đối tượng mà bạn đã tạo cần hiển thị một số phương thức mà thế giới bên ngoài có thể gọi để dọn sạch các tài nguyên không được quản lý. Phương pháp có thể được đặt tên theo bất cứ điều gì bạn thích:
public void Cleanup()
hoặc là
public void Shutdown()
Nhưng thay vào đó, có một tên được tiêu chuẩn hóa cho phương pháp này:
public void Dispose()
Thậm chí còn có một giao diện được tạo, IDisposable
chỉ có một phương thức đó:
public interface IDisposable
{
void Dispose()
}
Vì vậy, bạn làm cho đối tượng của mình hiển thị IDisposable
giao diện và theo cách đó bạn hứa rằng bạn đã viết phương thức duy nhất đó để dọn sạch các tài nguyên không được quản lý của bạn:
public void Dispose()
{
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
Và bạn đã hoàn thành. Ngoại trừ bạn có thể làm tốt hơn.
Điều gì sẽ xảy ra nếu đối tượng của bạn đã phân bổ System.Drawing.Bitmap (tức là lớp Bitmap được quản lý .NET) dưới dạng một số bộ đệm khung? Chắc chắn, đây là một đối tượng .NET được quản lý và trình thu gom rác sẽ giải phóng nó. Nhưng bạn có thực sự muốn để lại 250 MB bộ nhớ chỉ ngồi đó không - chờ người thu gom rác cuối cùng xuất hiện và giải phóng nó? Nếu có kết nối cơ sở dữ liệu mở thì sao? Chắc chắn chúng tôi không muốn kết nối đó mở ra, chờ đợi GC hoàn thành đối tượng.
Nếu người dùng đã gọi Dispose()
(có nghĩa là họ không còn có kế hoạch sử dụng đối tượng), tại sao không loại bỏ các bitmap lãng phí và kết nối cơ sở dữ liệu?
Vì vậy, bây giờ chúng tôi sẽ:
- loại bỏ các tài nguyên không được quản lý (vì chúng ta phải) và
- loại bỏ các tài nguyên được quản lý (vì chúng tôi muốn hữu ích)
Vì vậy, hãy cập nhật Dispose()
phương pháp của chúng tôi để loại bỏ các đối tượng được quản lý đó:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
Và tất cả đều tốt, ngoại trừ bạn có thể làm tốt hơn !
Điều gì nếu người đó quên gọi Dispose()
vào đối tượng của bạn? Sau đó, họ sẽ rò rỉ một số tài nguyên không được quản lý !
Lưu ý: Họ sẽ không rò rỉ tài nguyên được quản lý , vì cuối cùng trình thu gom rác sẽ chạy, trên một luồng nền và giải phóng bộ nhớ liên quan đến bất kỳ đối tượng không sử dụng nào. Điều này sẽ bao gồm đối tượng của bạn và bất kỳ đối tượng được quản lý nào bạn sử dụng (ví dụ: Bitmap
và DbConnection
).
Nếu người đó quên gọi Dispose()
, chúng tôi vẫn có thể cứu thịt xông khói của họ! Chúng tôi vẫn có một cách để gọi nó cho họ: khi người thu gom rác cuối cùng cũng có mặt để giải phóng (tức là hoàn thiện) đối tượng của chúng tôi.
Lưu ý: Trình thu gom rác cuối cùng sẽ giải phóng tất cả các đối tượng được quản lý. Khi đó, nó gọi Finalize
phương thức trên đối tượng. GC không biết, hoặc quan tâm, về phương pháp Vứt bỏ của bạn . Đó chỉ là một cái tên mà chúng tôi đã chọn cho một phương thức mà chúng tôi gọi khi chúng tôi muốn loại bỏ những thứ không được quản lý.
Sự phá hủy đối tượng của chúng ta bởi người thu gom Rác là thời điểm hoàn hảo để giải phóng những tài nguyên không được quản lý phiền phức đó. Chúng tôi làm điều này bằng cách ghi đè Finalize()
phương thức.
Lưu ý: Trong C #, bạn không ghi đè rõ ràng Finalize()
phương thức. Bạn viết một phương thức trông giống như một hàm hủy C ++ và trình biên dịch sẽ coi đó là triển khai Finalize()
phương thức của bạn:
~MyObject()
{
//we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning: subtle bug! Keep reading!
}
Nhưng có một lỗi trong mã đó. Bạn thấy, trình thu gom rác chạy trên một luồng nền ; bạn không biết thứ tự phá hủy hai vật thể. Hoàn toàn có thể trong Dispose()
mã của bạn , đối tượng được quản lý mà bạn đang cố gắng loại bỏ (vì bạn muốn trở nên hữu ích) không còn nữa:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
this.frameBufferImage = null;
}
}
Vì vậy, những gì bạn cần là một cách Finalize()
để nói Dispose()
rằng nó không nên chạm vào bất kỳ tài nguyên được quản lý nào (vì chúng có thể không còn ở đó nữa), trong khi vẫn giải phóng các tài nguyên không được quản lý.
Mẫu chuẩn để làm điều này là có Finalize()
và Dispose()
cả hai gọi phương thức thứ ba (!); nơi bạn vượt qua câu nói Boolean nếu bạn gọi nó từ Dispose()
(trái ngược với Finalize()
), nghĩa là nó an toàn đối với các tài nguyên được quản lý miễn phí.
Đây nội bộ phương pháp có thể được đưa ra một số tên tùy ý như "CoreDispose", hoặc "MyInternalDispose", nhưng là truyền thống để gọi nó là Dispose(Boolean)
:
protected void Dispose(Boolean disposing)
Nhưng một tên tham số hữu ích hơn có thể là:
protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too, but only if I'm being called from Dispose
//(If I'm being called from Finalize then the objects might not exist
//anymore
if (itIsSafeToAlsoFreeManagedObjects)
{
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
}
Và bạn thay đổi cách thực hiện IDisposable.Dispose()
phương thức thành:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
}
và quyết toán của bạn để:
~MyObject()
{
Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
Lưu ý : Nếu đối tượng của bạn hạ xuống từ một đối tượng thực hiện Dispose
, thì đừng quên gọi phương thức Vứt bỏ cơ sở của chúng khi bạn ghi đè Vứt bỏ:
public override void Dispose()
{
try
{
Dispose(true); //true: safe to free managed resources
}
finally
{
base.Dispose();
}
}
Và tất cả đều tốt, ngoại trừ bạn có thể làm tốt hơn !
Nếu người dùng gọi Dispose()
vào đối tượng của bạn, thì mọi thứ đã được dọn sạch. Sau đó, khi trình thu gom rác xuất hiện và gọi Finalize, nó sẽ gọi Dispose
lại.
Điều này không chỉ gây lãng phí mà còn nếu đối tượng của bạn có các tham chiếu rác đến các đối tượng bạn đã xử lý từ cuộc gọi cuối cùngDispose()
, bạn sẽ cố gắng loại bỏ chúng một lần nữa!
Bạn sẽ nhận thấy trong mã của mình Tôi đã cẩn thận xóa các tham chiếu đến các đối tượng mà tôi đã xử lý, vì vậy tôi không cố gắng gọi Dispose
tham chiếu đối tượng rác. Nhưng điều đó không ngăn được một con bọ tinh vi chui vào.
Khi người dùng gọi Dispose()
: tay cầm CthonFileBitmapIconServiceHandle bị hủy. Sau này khi trình thu gom rác chạy, nó sẽ cố gắng phá hủy cùng một xử lý một lần nữa.
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy
...
}
Cách bạn khắc phục điều này là nói với người thu gom rác rằng không cần bận tâm đến việc hoàn thiện đối tượng - tài nguyên của nó đã được dọn sạch và không cần thêm công việc nữa. Bạn làm điều này bằng cách gọi GC.SuppressFinalize()
trong Dispose()
phương thức:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}
Bây giờ người dùng đã gọi Dispose()
, chúng tôi có:
- giải phóng tài nguyên không được quản lý
- giải phóng tài nguyên được quản lý
Không có điểm nào trong GC chạy bộ hoàn thiện - mọi thứ đều được quan tâm.
Tôi không thể sử dụng Finalize để dọn dẹp các tài nguyên không được quản lý?
Các tài liệu cho Object.Finalize
biết:
Phương thức Finalize được sử dụng để thực hiện các hoạt động dọn dẹp trên các tài nguyên không được quản lý được giữ bởi đối tượng hiện tại trước khi đối tượng bị phá hủy.
Nhưng tài liệu MSDN cũng cho biết, cho IDisposable.Dispose
:
Thực hiện các tác vụ do ứng dụng xác định liên quan đến giải phóng, giải phóng hoặc đặt lại các tài nguyên không được quản lý.
Vậy đó là cái gì? Cái nào là nơi để tôi dọn dẹp các tài nguyên không được quản lý? Câu trả lời là:
Đó là lựa chọn của bạn! Nhưng chọn Dispose
.
Bạn chắc chắn có thể đặt dọn dẹp không được quản lý của bạn trong trình hoàn thiện:
~MyObject()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//A C# destructor automatically calls the destructor of its base class.
}
Vấn đề với điều đó là bạn không biết khi nào người dọn rác sẽ đi loanh quanh để hoàn thiện đối tượng của bạn. Các tài nguyên bản địa không được quản lý, không cần thiết, không được sử dụng của bạn sẽ tồn tại xung quanh cho đến khi trình thu gom rác cuối cùng chạy. Sau đó, nó sẽ gọi phương thức hoàn thiện của bạn; làm sạch tài nguyên không được quản lý. Tài liệu của Object.Finalize chỉ ra điều này:
Thời gian chính xác khi bộ hoàn thiện thực thi là không xác định. Để đảm bảo giải phóng tài nguyên xác định cho các phiên bản của lớp của bạn, hãy triển khai phương thức Đóng hoặc cung cấp IDisposable.Dispose
triển khai.
Đây là đức tính của việc sử dụng Dispose
để dọn dẹp các tài nguyên không được quản lý; bạn có thể biết và kiểm soát khi tài nguyên không được quản lý được dọn sạch. Sự hủy diệt của họ là "quyết định" .
Để trả lời câu hỏi ban đầu của bạn: Tại sao không giải phóng bộ nhớ ngay bây giờ, thay vì khi GC quyết định làm điều đó? Tôi có một phần mềm nhận dạng khuôn mặt mà nhu cầu để thoát khỏi 530 MB bộ ảnh nội tại , vì họ không còn cần thiết. Khi chúng ta không: máy ngừng hoạt động.
Đọc thưởng
Đối với bất kỳ ai thích phong cách của câu trả lời này (giải thích lý do tại sao , làm thế nào để trở nên rõ ràng), tôi khuyên bạn nên đọc Chương Một của COM Box thiết yếu của Don Box:
Trong 35 trang, anh giải thích các vấn đề của việc sử dụng các đối tượng nhị phân và phát minh ra COM trước mắt bạn. Khi bạn nhận ra lý do của COM, 300 trang còn lại là hiển nhiên và chỉ chi tiết triển khai của Microsoft.
Tôi nghĩ rằng tất cả các lập trình viên đã từng xử lý các đối tượng hoặc COM, ít nhất, nên đọc chương đầu tiên. Đó là lời giải thích tốt nhất của bất cứ điều gì bao giờ.
Đọc thêm tiền thưởng
Khi mọi thứ bạn biết là sai bởi Eric Lippert
Do đó, thực sự rất khó để viết một bộ hoàn thiện chính xác, và lời khuyên tốt nhất tôi có thể cung cấp cho bạn là không thử .