Delete và deleteLater hoạt động như thế nào đối với các tín hiệu và vị trí trong Qt?


78

Có một đối tượng của lớp QNetworkReply. Có một khe (trong một số đối tượng khác) được kết nối với tín hiệu () đã hoàn thành của nó. Các tín hiệu là đồng bộ (mặc định). Chỉ có một chủ đề.

Tại một thời điểm nào đó tôi muốn loại bỏ cả hai đối tượng. Không có thêm tín hiệu hoặc bất cứ điều gì từ chúng. Tôi muốn chúng biến mất. Tôi nghĩ, tôi sẽ sử dụng

delete obj1; delete obj2;

Nhưng tôi thực sự có thể? Các thông số kỹ thuật cho ~ QObject nói:

Xóa QObject trong khi các sự kiện đang chờ xử lý đang chờ được phân phối có thể gây ra sự cố.

'Sự kiện đang chờ xử lý' là gì? Điều đó có thể có nghĩa là trong khi tôi đang gọi điện delete, đã có một số 'sự kiện đang chờ xử lý' được gửi đến và chúng có thể gây ra sự cố và tôi không thể thực sự kiểm tra xem có sự kiện nào không?

Vì vậy, giả sử tôi gọi:

obj1->deleteLater(); obj2->deleteLater();

Để được an toàn.

Nhưng, tôi có thực sự an toàn? Thêm deleteLatermột sự kiện sẽ được xử lý trong vòng lặp chính khi điều khiển đến đó. Có thể có một số sự kiện (tín hiệu) đang chờ xử lý cho obj1hoặc obj2đã ở đó, đang chờ xử lý trong vòng lặp chính trước khi deleteLater sẽ được xử lý không? Điều đó sẽ rất đáng tiếc. Tôi không muốn viết mã kiểm tra trạng thái 'phần nào đã bị xóa' và bỏ qua tín hiệu đến trong tất cả các vị trí của tôi.


4
Có vẻ như đây obj->disconnect(); obj->deleteLater();là cách phù hợp để đi:
stach

1
Sau khi đọc nguồn QObject, có vẻ như deleteLater()chỉ cần đăng một QDeferredDeleteEventđối tượng deleteLater()được gọi vào. Khi sự kiện đó được QObject tiếp nhận, trình xử lý sự kiện của nó cuối cùng sẽ gọi chính quy deletemà lần lượt gọi hàm hủy của QObject. Việc ngắt kết nối tín hiệu không xảy ra cho đến khi kết thúc trình hủy, do đó tôi đoán rằng QObject sẽ chạy các khe được gọi bởi các tín hiệu DirectConnection được phát ra sau cuộc gọi đến deleteLater()nhưng trước khi vòng lặp sự kiện quay trở lại.
Kasheen

Câu trả lời:


74

Xóa QObjects thường an toàn (tức là trong thực tế bình thường; có thể có những trường hợp bệnh lý mà tôi không biết về atm), nếu bạn tuân theo hai quy tắc cơ bản:

  • Không bao giờ xóa một đối tượng trong một vị trí hoặc phương thức được gọi trực tiếp hoặc gián tiếp bằng tín hiệu (đồng bộ, kiểu kết nối "trực tiếp") khỏi đối tượng sẽ bị xóa. Ví dụ: nếu bạn có một lớp Hoạt động với một tín hiệu Hoạt động :: xong () và một Trình quản lý vị trí :: hoạt động Hoàn tất (), bạn không muốn xóa đối tượng hoạt động đã phát ra tín hiệu trong vị trí đó. Phương thức phát ra tín hiệu finish () có thể tiếp tục truy cập "this" sau khi phát ra (ví dụ: truy cập một thành viên), và sau đó hoạt động trên một con trỏ "this" không hợp lệ.

  • Tương tự như vậy, không bao giờ xóa một đối tượng trong mã được gọi đồng bộ khỏi trình xử lý sự kiện của đối tượng. Ví dụ: không xóa SomeWidget trong SomeWidget :: fooEvent () của nó hoặc trong các phương thức / vị trí bạn gọi từ đó. Hệ thống sự kiện sẽ tiếp tục hoạt động trên đối tượng đã bị xóa -> Crash.

Cả hai đều có thể khó theo dõi, vì các dấu vết thường trông kỳ lạ (Như sự cố trong khi truy cập biến thành viên POD), đặc biệt khi bạn có chuỗi tín hiệu / vị trí phức tạp trong đó việc xóa có thể xảy ra một số bước xuống ban đầu do một tín hiệu hoặc sự kiện từ đối tượng bị xóa.

Những trường hợp như vậy là trường hợp sử dụng phổ biến nhất cho deleteLater (). Nó đảm bảo rằng sự kiện hiện tại có thể được hoàn thành trước khi điều khiển quay trở lại vòng lặp sự kiện, sau đó sẽ xóa đối tượng. Một cách khác, tôi thường thấy cách tốt hơn là trì hoãn toàn bộ hành động bằng cách sử dụng một kết nối được xếp hàng đợi / QMetaObject :: invokeMethod (..., Qt :: QueuedConnection).


1
Một ví dụ về sự cố này sẽ là: từ sự kiện focusOut của một số widget, tôi xóa một số widget con. Tiêu điểm được kích hoạt bằng một cú nhấp chuột vào một trong các tiện ích con cần xóa. Trong ví dụ này, xóa không an toàn vì khi đến vòng lặp sự kiện, đối tượng đã biến mất và gây ra sự cố khi nó cố gắng phân phối một sự kiện nhấp chuột tới tiện ích con đó. deleteLater là an toàn vì đối tượng được đánh dấu để xóa và vòng lặp sự kiện biết rằng sự kiện này không nên được phân phối vì đối tượng đã bị xóa
AKludges

22

Hai dòng tiếp theo của tài liệu đã giới thiệu của bạn cho biết câu trả lời.

Từ ~ QObject ,

Xóa QObject trong khi các sự kiện đang chờ xử lý đang chờ được phân phối có thể gây ra sự cố. Bạn không được xóa QObject trực tiếp nếu nó tồn tại trong một luồng khác với luồng hiện đang thực thi. Thay vào đó, hãy sử dụng deleteLater (), điều này sẽ khiến vòng lặp sự kiện xóa đối tượng sau khi tất cả các sự kiện đang chờ xử lý đã được chuyển đến nó.

Nó đặc biệt nói rằng chúng tôi không được xóa khỏi các chủ đề khác. Vì bạn có một ứng dụng theo luồng duy nhất, nên xóa an toàn QObject.

Ngược lại, nếu bạn phải xóa nó trong môi trường đa luồng, hãy sử dụng tính năng deleteLater()này sẽ xóa của bạn QObjectsau khi xử lý xong tất cả các sự kiện.


6
Còn về kịch bản thứ hai của tôi? Các vị trí trong một đối tượng có thể vẫn được gọi sau khi tôi gọi deleteLater trên nó không?
stach

15

Bạn có thể tìm thấy câu trả lời cho câu hỏi của mình khi đọc về một trong các Quy tắc đối tượng Delta nêu rõ điều này:

Tín hiệu an toàn (SS).
Phải an toàn khi gọi các phương thức trên đối tượng, bao gồm cả hàm hủy, từ bên trong một vị trí được gọi bởi một trong các tín hiệu của nó.

Miếng:

Về cốt lõi, QObject hỗ trợ bị xóa trong khi báo hiệu. Để tận dụng lợi thế của nó, bạn chỉ cần chắc chắn rằng đối tượng của bạn không cố gắng truy cập bất kỳ thành viên nào của chính nó sau khi bị xóa. Tuy nhiên, hầu hết các đối tượng Qt không được viết theo cách này, và không có yêu cầu nào đối với chúng. Vì lý do này, bạn nên luôn gọi deleteLater () nếu bạn cần xóa một đối tượng trong một trong các tín hiệu của nó, bởi vì tỷ lệ cược là 'xóa' sẽ chỉ làm hỏng ứng dụng.

Thật không may, không phải lúc nào cũng rõ ràng khi nào bạn nên sử dụng 'delete' so với deleteLater (). Nghĩa là, không phải lúc nào đường dẫn mã cũng có nguồn tín hiệu rõ ràng. Thông thường, bạn có thể có một khối mã sử dụng 'xóa' trên một số đối tượng an toàn hiện nay, nhưng tại một số thời điểm trong tương lai, khối mã tương tự này kết thúc được gọi từ một nguồn tín hiệu và bây giờ đột nhiên ứng dụng của bạn bị lỗi. Giải pháp chung duy nhất cho vấn đề này là sử dụng deleteLater () mọi lúc, ngay cả khi trong nháy mắt, nó có vẻ không cần thiết.

Nói chung, tôi coi Quy tắc đối tượng Delta là bắt buộc phải đọc đối với mọi nhà phát triển Qt. Đó là tài liệu đọc tuyệt vời.


1
Nếu bạn theo liên kết đến DOR, bạn phải theo các liên kết trên trang đó để đọc thêm, ví dụ: theo liên kết đến 'Signal Safe.' Trang đầu tiên từ liên kết rất khó hiểu nếu không có ngữ cảnh. (Tôi đang theo đuổi một vụ tai nạn trên lối sử dụng PyQt trên Windows, ứng dụng của tôi thậm chí không xóa bất kỳ đối tượng, nhưng tôi hy vọng các liên kết đến DOR sẽ cung cấp những hiểu biết.)
bootchk

4

Theo như tôi biết, đây chủ yếu là một vấn đề nếu các đối tượng tồn tại trong các chủ đề khác nhau. Hoặc có thể trong khi bạn đang thực sự xử lý các tín hiệu.

Nếu không, việc xóa QObject trước tiên sẽ ngắt kết nối tất cả các tín hiệu và vị trí và xóa tất cả các sự kiện đang chờ xử lý. Như một cuộc gọi để ngắt kết nối () sẽ làm.

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.