Khi nào nên sử dụng tài liệu tham khảo yếu trong .Net?


56

Cá nhân tôi không gặp phải tình huống tôi cần sử dụng loại WeakReference trong .Net, nhưng niềm tin phổ biến dường như là nó nên được sử dụng trong bộ nhớ cache. Tiến sĩ Jon Harrop đã đưa ra một trường hợp rất tốt chống lại việc sử dụng WeakReferences trong bộ nhớ cache trong câu trả lời của ông cho câu hỏi này .

Tôi cũng thường nghe các nhà phát triển AS3 nói về việc sử dụng các tài liệu tham khảo yếu để tiết kiệm dung lượng bộ nhớ nhưng dựa trên các cuộc hội thoại mà tôi có vẻ như đã thêm phức tạp mà không nhất thiết phải hoàn thành mục tiêu dự định và hành vi thời gian chạy là không thể đoán trước. Nhiều đến mức nhiều người chỉ đơn giản từ bỏ nó và thay vào đó quản lý việc sử dụng bộ nhớ một cách cẩn thận hơn / tối ưu hóa mã của họ để ít sử dụng bộ nhớ hơn (hoặc đánh đổi nhiều chu kỳ CPU hơn và dung lượng bộ nhớ nhỏ hơn).

Tiến sĩ Jon Harrop cũng chỉ ra trong câu trả lời của mình rằng các tài liệu tham khảo yếu .Net không mềm mại và có một bộ sưu tập tích cực các tài liệu tham khảo yếu ở gen0. Theo MSDN , các tài liệu tham khảo yếu dài cho bạn tiềm năng để tạo lại một đối tượng , but the state of the object remains unpredictable.!

Với những đặc điểm này, tôi không thể nghĩ đến một tình huống mà các tài liệu tham khảo yếu sẽ hữu ích, có lẽ ai đó có thể khai sáng cho tôi?


3
Bạn đã vạch ra những công dụng tiềm năng cho nó. Tất nhiên có nhiều cách khác để tiếp cận những tình huống này, nhưng có nhiều hơn một cách để lột da một con mèo. Nếu bạn đang tìm kiếm một viên đạn "bạn nên luôn luôn sử dụng WeakReference khi X", tôi nghi ngờ bạn sẽ tìm thấy một viên đạn.

2
@ Itsme86 - Tôi không tìm kiếm trường hợp sử dụng chống đạn, chỉ những trường hợp tham chiếu yếu là phù hợp và có ý nghĩa. Chẳng hạn như trường hợp sử dụng bộ đệm, vì các tham chiếu yếu được thu thập rất háo hức, nó sẽ gây ra nhiều lỗi nhớ cache hơn ngay cả khi bạn có sẵn nhiều bộ nhớ

4
Tôi hơi thất vọng vì điều này nhận được nhiều phiếu bầu để đóng. Tôi sẽ không thấy câu trả lời hoặc một số cuộc thảo luận về vấn đề này (trong b4 "Stack Overflow không phải là một diễn đàn").
ta.speot.is

@theburnmonk Đó là sự bù đắp cho việc tăng bộ nhớ. Trong khuôn khổ ngày nay, có nghi ngờ rằng bất kỳ ai cũng sẽ tiếp cận trực tiếp với công cụ WeakReference ngay cả khi triển khai bộ đệm vì có sẵn các hệ thống bộ đệm ẩn toàn diện.

Dưới đây là một ví dụ xấu hổ quá phức tạp của việc sử dụng chúng (đối với mẫu tổ chức sự kiện Yếu rằng ta.speot.is mô tả dưới đây)
Benjol

Câu trả lời:


39

Tôi đã tìm thấy các ứng dụng thực tế hợp pháp của các tài liệu tham khảo yếu trong ba tình huống thực tế sau đây thực sự xảy ra với cá nhân tôi:

Ứng dụng 1: Xử lý sự kiện

Bạn là một doanh nhân. Công ty của bạn bán một điều khiển tia lửa cho WPF. Bán hàng là rất lớn nhưng chi phí hỗ trợ đang giết chết bạn. Quá nhiều khách hàng phàn nàn về việc CPU bị rò rỉ và rò rỉ bộ nhớ khi họ cuộn qua màn hình đầy những tia lửa. Vấn đề là ứng dụng của họ đang tạo ra các dòng tia lửa mới khi chúng xuất hiện nhưng ràng buộc dữ liệu đang ngăn những cái cũ bị thu gom rác. Bạn làm nghề gì?

Giới thiệu một tham chiếu yếu giữa ràng buộc dữ liệu và kiểm soát của bạn để ràng buộc dữ liệu một mình sẽ không còn ngăn chặn kiểm soát của bạn bị thu thập rác. Sau đó, thêm một bộ hoàn thiện vào điều khiển của bạn để phá vỡ ràng buộc dữ liệu khi nó được thu thập.

Ứng dụng 2: Đồ thị có thể thay đổi

Bạn là John Carmack tiếp theo. Bạn đã phát minh ra một đại diện mới dựa trên đồ thị của các bề mặt phân chia phân cấp làm cho các trò chơi của Tim Sweeney trông giống như Nintendo Wii. Rõ ràng tôi sẽ không nói cho bạn biết chính xác nó hoạt động như thế nào nhưng tất cả đều tập trung vào biểu đồ có thể thay đổi này, nơi các hàng xóm của một đỉnh có thể được tìm thấy trong a Dictionary<Vertex, SortedSet<Vertex>>. Cấu trúc liên kết của đồ thị tiếp tục thay đổi khi người chơi chạy xung quanh. Chỉ có một vấn đề: cấu trúc dữ liệu của bạn sẽ loại bỏ các sơ đồ con không thể truy cập khi nó chạy và bạn cần xóa chúng hoặc bạn sẽ bị rò rỉ bộ nhớ. May mắn thay, bạn là một thiên tài, vì vậy bạn biết có một loại thuật toán được thiết kế đặc biệt để định vị và thu thập các sơ đồ con không thể truy cập: người thu gom rác! Bạn đọc chuyên khảo xuất sắc của Richard Jones về chủ đề nàynhưng nó khiến bạn bối rối và lo lắng về thời hạn sắp xảy ra. Bạn làm nghề gì?

Đơn giản bằng cách thay thế Dictionarybảng băm yếu của bạn, bạn có thể cõng GC hiện có và để nó tự động thu thập các sơ đồ con không thể truy cập của bạn cho bạn! Trở lại lá thông qua quảng cáo Ferrari.

Ứng dụng 3: Trang trí cây xanh

Bạn đang treo trên trần của một căn phòng hình tròn ở bàn phím. Bạn đã có 60 giây để sàng lọc một số DỮ LIỆU LỚN trước khi ai đó tìm thấy bạn. Bạn đã chuẩn bị sẵn một trình phân tích cú pháp dựa trên luồng tuyệt đẹp dựa trên GC để thu thập các mảnh AST sau khi chúng được phân tích. Nhưng bạn nhận ra rằng bạn cần thêm siêu dữ liệu trên mỗi AST Nodevà bạn cần nó nhanh chóng. Bạn làm nghề gì?

Bạn có thể sử dụng một Dictionary<Node, Metadata>siêu dữ liệu để liên kết với mỗi nút, nhưng trừ khi bạn xóa nó, các tham chiếu mạnh từ từ điển đến các nút AST cũ sẽ giữ cho chúng tồn tại và rò rỉ bộ nhớ. Giải pháp là một bảng băm yếu chỉ giữ các tham chiếu yếu đến các khóa và rác thu thập các ràng buộc giá trị khóa khi khóa không thể truy cập được. Sau đó, khi các nút AST trở nên không thể truy cập được, chúng là rác được thu thập và ràng buộc khóa-giá trị của chúng bị xóa khỏi từ điển khiến siêu dữ liệu tương ứng không thể truy cập được để nó cũng được thu thập. Sau đó, tất cả những gì bạn phải làm sau khi vòng lặp chính của bạn kết thúc là trượt trở lại qua lỗ thông hơi để nhớ thay thế nó giống như nhân viên bảo vệ đi vào.

Lưu ý rằng trong cả ba ứng dụng trong thế giới thực này xảy ra với tôi, tôi muốn GC thu thập mạnh nhất có thể. Đó là lý do tại sao đây là những ứng dụng hợp pháp. Mọi người khác đều sai.


2
Tài liệu tham khảo yếu sẽ không hoạt động cho ứng dụng 2 nếu các sơ đồ con không thể truy cập được chứa các chu kỳ. Điều này là do bảng băm yếu thường có tham chiếu yếu đến các khóa, nhưng tham chiếu mạnh đến các giá trị. Bạn sẽ cần một bảng băm chỉ duy trì các tham chiếu mạnh đến các giá trị trong khi khóa vẫn có thể truy cập được -> xem các biểu tượng ( ConditionalWeakTabletrong .NET).
Daniel

@Daniel Không phải là GC được cho là có thể xử lý các chu kỳ không thể truy cập? Làm thế nào điều này sẽ không được thu thập khi một chu kỳ mạnh mẽ của các tài liệu tham khảo mạnh mẽ sẽ được thu thập?
binki

Ồ, tôi nghĩ rằng tôi thấy. Tôi chỉ cho rằng đó ConditionalWeakTablelà những gì ứng dụng 2 và 3 sẽ sử dụng trong khi một số người trong các bài đăng khác thực sự sử dụng Dictionary<WeakReference, T>. Không có lý do tại sao mà bạn luôn luôn kết thúc với một tấn null WeakReferencevới các giá trị không thể được truy cập bởi bất kỳ khóa nào bất kể bạn làm như thế nào. Ridik.
binki

@binki: "Không phải là GC được cho là có thể xử lý các chu kỳ không thể truy cập được? Làm thế nào điều này sẽ không được thu thập khi một chu kỳ không thể truy cập của các tài liệu tham khảo mạnh mẽ sẽ được thu thập?". Bạn có một từ điển được khóa trên các đối tượng duy nhất không thể được tạo lại. Khi một trong những đối tượng chính của bạn không thể truy cập được, nó có thể là rác được thu thập nhưng giá trị tương ứng trong từ điển thậm chí sẽ không thể nghĩ rằng nó không thể truy cập được về mặt lý thuyết bởi vì một từ điển thông thường sẽ giữ tham chiếu mạnh đến nó, giữ cho nó tồn tại. Vì vậy, bạn sử dụng một từ điển yếu.
Jon Harrop

@Daniel: "Tham chiếu yếu sẽ không hoạt động cho ứng dụng 2 nếu các sơ đồ con không thể truy cập chứa chu kỳ. Điều này là do bảng băm yếu thường có các tham chiếu yếu đến các khóa, nhưng tham chiếu mạnh đến các giá trị. Bạn cần một bảng băm. chỉ duy trì các tham chiếu mạnh đến các giá trị trong khi khóa vẫn có thể truy cập được ". Đúng. Có lẽ tốt hơn hết bạn nên mã hóa biểu đồ trực tiếp bằng các cạnh như con trỏ để GC sẽ tự thu thập nó.
Jon Harrop

19

Với những đặc điểm này, tôi không thể nghĩ đến một tình huống mà các tài liệu tham khảo yếu sẽ hữu ích, có lẽ ai đó có thể khai sáng cho tôi?

Tài liệu của Microsoft Các mẫu sự kiện yếu .

Trong các ứng dụng, có thể các trình xử lý được gắn vào các nguồn sự kiện sẽ không bị hủy khi phối hợp với đối tượng người nghe đã gắn trình xử lý với nguồn. Tình trạng này có thể dẫn đến rò rỉ bộ nhớ. Windows Presentation Foundation (WPF) giới thiệu một mẫu thiết kế có thể được sử dụng để giải quyết vấn đề này, bằng cách cung cấp một lớp quản lý chuyên dụng cho các sự kiện cụ thể và triển khai giao diện cho người nghe cho sự kiện đó. Mẫu thiết kế này được gọi là mẫu sự kiện yếu.

...

Mẫu sự kiện yếu được thiết kế để giải quyết vấn đề rò rỉ bộ nhớ này. Mẫu sự kiện yếu có thể được sử dụng bất cứ khi nào người nghe cần đăng ký cho một sự kiện, nhưng người nghe không biết rõ khi nào nên hủy đăng ký. Mẫu sự kiện yếu cũng có thể được sử dụng bất cứ khi nào tuổi thọ đối tượng của nguồn vượt quá tuổi thọ đối tượng hữu ích của người nghe. (Trong trường hợp này, tính hữu ích được xác định bởi bạn.) Mẫu sự kiện yếu cho phép người nghe đăng ký và nhận sự kiện mà không ảnh hưởng đến đặc tính trọn đời đối tượng của người nghe theo bất kỳ cách nào. Trong thực tế, tham chiếu ngụ ý từ nguồn không xác định liệu người nghe có đủ điều kiện để thu gom rác hay không. Tham chiếu là một tham chiếu yếu, do đó, việc đặt tên cho mẫu sự kiện yếu và các API liên quan. Người nghe có thể là rác được thu thập hoặc bị phá hủy, và nguồn có thể tiếp tục mà không giữ lại các tham chiếu xử lý không thể thu thập đến một đối tượng đã bị phá hủy.


URL tự động đó chọn phiên bản .NET mới nhất (4.5 hiện tại) trong đó 'chủ đề này không còn khả dụng'. Thay vào đó, chọn .NET 4.0 hoạt động ( msdn.microsoft.com/en-us/l Library / aa970850 ( v = vs.100 ) .aspx )
maxp

13

Hãy để tôi đưa nó ra trước và quay lại với nó:

WeakReference rất hữu ích khi bạn muốn giữ các tab trên một đối tượng, nhưng bạn KHÔNG muốn các quan sát của mình ngăn chặn đối tượng đó được thu thập

Vì vậy, hãy bắt đầu lại từ đầu:

--apology trước cho bất kỳ hành vi phạm tội vô ý, nhưng tôi sẽ trở lại mức "Dick và Jane" trong một khoảnh khắc vì người ta không bao giờ có thể nói với khán giả.

Vì vậy, khi bạn đã có một đối tượng X- hãy xác định nó là một ví dụ của class Foo- nó KHÔNG THỂ tự sống (chủ yếu là sự thật); Theo cùng một cách "Không có ai là một hòn đảo", chỉ có một vài cách mà một đối tượng có thể thăng cấp lên Đảo - mặc dù nó được gọi là gốc GC trong CLR. Là một gốc Root, hoặc có một chuỗi các kết nối / tham chiếu được thiết lập đến gốc GC, về cơ bản là điều quyết định việc Foo x = new Foo()có thu gom rác hay không .

Nếu bạn không thể đi bộ trở lại một số gốc GC bằng cách đi bộ hoặc chồng, bạn sẽ mồ côi một cách hiệu quả, và có khả năng sẽ được đánh dấu / thu thập chu kỳ tiếp theo.

Tại thời điểm này, chúng ta hãy xem xét một số ví dụ kinh khủng:

Đầu tiên, chúng tôi Foo:

public class Foo 
{
    private static volatile int _ref = 0;
    public event EventHandler FooEvent;
    public Foo()
    {
        _ref++;
        Console.WriteLine("I am #{0}", _ref);
    }
    ~Foo()
    {
        Console.WriteLine("#{0} dying!", _ref--);
    }
}

Khá đơn giản - nó không an toàn cho chủ đề, vì vậy đừng thử điều đó, nhưng vẫn giữ một "số tham chiếu" sơ bộ của các trường hợp hoạt động và giảm dần khi chúng được hoàn thành.

Bây giờ hãy xem xét một FooConsumer:

public class NastySingleton
{
    // Static member status is one way to "get promoted" to a GC root...
    private static NastySingleton _instance = new NastySingleton();
    public static NastySingleton Instance { get { return _instance;} }

    // testing out "Hard references"
    private Dictionary<Foo, int> _counter = new Dictionary<Foo,int>();
    // testing out "Weak references"
    private Dictionary<WeakReference, int> _weakCounter = new Dictionary<WeakReference,int>();

    // Creates a strong link to Foo instance
    public void ListenToThisFoo(Foo foo)
    {
        _counter[foo] = 0;
        foo.FooEvent += (o, e) => _counter[foo]++;
    }

    // Creates a weak link to Foo instance
    public void ListenToThisFooWeakly(Foo foo)
    {
        WeakReference fooRef = new WeakReference(foo);
        _weakCounter[fooRef] = 0;
        foo.FooEvent += (o, e) => _weakCounter[fooRef]++;
    }

    private void HandleEvent(object sender, EventArgs args, Foo originalfoo)
    {
        Console.WriteLine("Derp");
    }
}

Vì vậy, chúng ta đã có một đối tượng đã là gốc GC của chính nó (cụ thể là ... cụ thể, nó sẽ được bắt nguồn từ một chuỗi thẳng đến miền ứng dụng chạy ứng dụng này, nhưng đó là một chủ đề khác) có hai phương thức về việc chốt một Fooví dụ - hãy kiểm tra nó:

// Our foo
var f = new Foo();

// Create a "hard reference"
NastySingleton.Instance.ListenToThisFoo(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Bây giờ, từ những điều trên, bạn có mong muốn đối tượng đã từng được nhắc fđến là "có thể sưu tập" không?

Không, bởi vì có một đối tượng khác hiện đang giữ một tham chiếu đến nó - Dictionarytrong Singletontrường hợp tĩnh đó .

Ok, chúng ta hãy thử cách tiếp cận yếu:

f = new Foo();
NastySingleton.Instance.ListenToThisFooWeakly(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
// This should collect # 2 - you'll see a "#2 dying"
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Bây giờ, khi chúng tôi đưa tài liệu tham khảo của chúng tôi vào Foo- đó là một lần- f, không có tài liệu tham khảo "cứng" nào nữa cho đối tượng, vì vậy nó có thể được thu thập - WeakReferencedo người nghe yếu tạo ra sẽ không ngăn chặn điều đó.

Các trường hợp sử dụng tốt:

  • Trình xử lý sự kiện (Mặc dù đọc phần này trước: Sự kiện yếu trong C # )

  • Bạn đã có một tình huống trong đó bạn sẽ gây ra một "tham chiếu đệ quy" (nghĩa là đối tượng A đề cập đến đối tượng B, đề cập đến đối tượng A, còn được gọi là "Rò rỉ bộ nhớ") (chỉnh sửa: derp, tất nhiên đây không phải là 'đúng)

  • Bạn muốn "phát" một cái gì đó đến một bộ sưu tập các đối tượng, nhưng bạn không muốn là thứ giữ cho chúng tồn tại; một List<WeakReference>có thể được duy trì dễ dàng, và thậm chí cắt tỉa bằng cách loại bỏ nơiref.Target == null


1
Về trường hợp sử dụng thứ hai của bạn, trình thu gom rác xử lý các tham chiếu vòng tròn tốt. "đối tượng A đề cập đến đối tượng B, liên quan đến đối tượng A" chắc chắn không phải là rò rỉ bộ nhớ.
Joe Daley

@JoeDaley Tôi đồng ý. .NET GC sử dụng thuật toán đánh dấu và quét (tôi tin rằng tôi nhớ chính xác điều này) đánh dấu tất cả các đối tượng để thu thập và sau đó theo các tham chiếu từ "gốc" (tham chiếu của các đối tượng trên ngăn xếp, các đối tượng tĩnh), bỏ đánh dấu các đối tượng cho bộ sưu tập . Nếu một tham chiếu tròn tồn tại nhưng không có đối tượng nào có thể truy cập được từ gốc, các đối tượng không được đánh dấu để thu thập và do đó đủ điều kiện để thu thập.
ta.speot.is

1
@JoeDaley - Tất nhiên, cả hai bạn đều đúng - đã vội vã đưa nó đến cuối cùng ... Tôi sẽ chỉnh sửa nó ra.
JerKimball

4

nhập mô tả hình ảnh ở đây

Giống như các rò rỉ logic rất khó theo dõi trong khi người dùng chỉ có xu hướng nhận thấy rằng việc chạy phần mềm của bạn trong một thời gian dài có xu hướng chiếm nhiều bộ nhớ hơn và ngày càng chậm hơn cho đến khi họ khởi động lại? Tôi không.

Xem xét những gì xảy ra nếu, khi người dùng yêu cầu xóa tài nguyên ứng dụng ở trên, Thing2không xử lý đúng sự kiện như vậy trong:

  1. Con trỏ
  2. Tài liệu tham khảo mạnh mẽ
  3. Tài liệu tham khảo yếu

... và theo đó một trong những sai lầm như vậy có thể sẽ bị bắt trong quá trình thử nghiệm, và lỗi nào sẽ không và sẽ bay dưới radar như một con bọ chiến đấu tàng hình. Sở hữu chung là, thường xuyên hơn hầu hết, là một ý tưởng vô nghĩa.


1

Một ví dụ rất minh họa về các tham chiếu yếu được sử dụng để có hiệu quả tốt là Cond điều kiện WeakTable , được DLR (trong số các vị trí khác) sử dụng để gắn thêm "thành viên" vào các đối tượng.

Bạn không muốn cái bàn giữ cho vật thể sống. Khái niệm này chỉ đơn giản là không thể làm việc mà không có tài liệu tham khảo yếu.

Nhưng đối với tôi, dường như tất cả các cách sử dụng cho các tham chiếu yếu đã xuất hiện rất lâu sau khi chúng được thêm vào ngôn ngữ, vì các tham chiếu yếu là một phần của .NET kể từ phiên bản 1.1. Nó chỉ giống như một cái gì đó bạn muốn thêm vào, do đó, việc thiếu sự phá hủy mang tính quyết định sẽ không đưa bạn vào một góc khi có liên quan đến các tính năng ngôn ngữ.


Tôi thực sự đã phát hiện ra rằng mặc dù bảng sử dụng khái niệm tham chiếu yếu, nhưng việc triển khai thực tế không liên quan đến WeakReferencekiểu này, vì tình hình phức tạp hơn rất nhiều. Nó sử dụng các chức năng khác nhau được phơi bày bởi CLR.
GregRos

-2

Nếu bạn có lớp bộ đệm được triển khai với C #, tốt hơn hết là đặt dữ liệu của bạn vào bộ đệm làm tham chiếu yếu, điều đó có thể giúp cải thiện hiệu suất của lớp bộ đệm của bạn.

Hãy nghĩ rằng cách tiếp cận cũng có thể được áp dụng để thực hiện phiên. Bởi vì phiên là đối tượng sống lâu trong hầu hết thời gian, có thể là một số trường hợp khi bạn không có bộ nhớ cho người dùng mới. Trong trường hợp đó, sẽ tốt hơn nhiều nếu xóa một số đối tượng phiên người dùng khác sau đó ném OutOfMemoryException.

Ngoài ra, nếu bạn có một đối tượng lớn trong ứng dụng của mình (một số bảng tra cứu lớn, v.v.), thì việc đó nên được sử dụng khá hiếm khi và việc tạo lại một đối tượng như vậy không phải là một thủ tục rất tốn kém. Sau đó, tốt hơn là nó giống như một tài liệu tham khảo trong tuần để có một cách để giải phóng bộ nhớ của bạn khi bạn thực sự cần điều đó.


5
nhưng vấn đề với các tài liệu tham khảo yếu (xem câu trả lời tôi đã tham khảo) là chúng được thu thập rất háo hức và bộ sưu tập không được liên kết với sự sẵn có của không gian bộ nhớ. Vì vậy, bạn kết thúc với nhiều bộ nhớ cache hơn khi không có áp lực lên bộ nhớ.

1
Nhưng đối với quan điểm thứ hai của bạn về các đối tượng lớn, tài liệu MSDN nói rằng trong khi các tham chiếu yếu dài cho phép bạn tạo lại một đối tượng, thì trạng thái đó vẫn không thể đoán trước được. Nếu bạn sẽ tạo lại nó từ đầu mỗi lần, tại sao phải sử dụng một tham chiếu yếu khi bạn chỉ có thể gọi một hàm / phương thức để tạo nó theo yêu cầu và trả về một thể hiện thoáng qua?

Có một tình huống trong đó bộ nhớ đệm rất hữu ích: Nếu một người sẽ thường xuyên tạo các đối tượng bất biến, nhiều trong số đó sẽ xảy ra giống hệt nhau (ví dụ đọc nhiều dòng từ một tệp dự kiến ​​có nhiều bản sao), mỗi chuỗi sẽ được tạo như một đối tượng mới , nhưng nếu một dòng khớp với một dòng khác mà tham chiếu đã tồn tại, hiệu quả bộ nhớ có thể được cải thiện nếu thể hiện mới bị bỏ qua và một tham chiếu đến thể hiện tồn tại trước được thay thế. Lưu ý rằng sự thay thế này là hữu ích vì dù sao tham chiếu khác đang được tổ chức. Nếu nó không có mã thì nên giữ cái mới.
supercat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.