2010-01-12 27 views
11

Tôi đã tìm thấy rằng tăng :: signal2 sử dụng loại bỏ một cách lười biếng các khe được kết nối, điều này làm cho việc sử dụng các kết nối trở nên khó khăn như điều gì đó quản lý thời gian sống của các đối tượng. Tôi đang tìm một cách để buộc các khe bị xóa trực tiếp khi bị ngắt kết nối. Bất kỳ ý tưởng về cách làm việc xung quanh vấn đề bằng cách thiết kế mã của tôi khác nhau cũng được đánh giá cao!Bắt buộc xóa khe trong tăng :: signal2

Đây là kịch bản của tôi: Tôi có một lớp học chỉ huy chịu trách nhiệm về thực hiện một cái gì đó mà cần có thời gian không đồng bộ, tìm kiếm một cái gì đó như thế này (giản thể):

class ActualWorker { 
public: 
    boost::signals2<void()> OnWorkComplete; 
}; 

class Command : boost::enable_shared_from_this<Command> { 
public: 
    ... 

    void Execute() { 
     m_WorkerConnection = m_MyWorker.OnWorkDone.connect(boost::bind(&Command::Handle_OnWorkComplete, shared_from_this()); 

     // launch asynchronous work here and return 
    } 

    boost::signals2<void()> OnComplete; 

private: 
    void Handle_OnWorkComplete() { 
     // get a shared_ptr to ourselves to make sure that we live through 
     // this function but don't keep ourselves alive if an exception occurs. 
     shared_ptr<Command> me = shared_from_this(); 

     // Disconnect from the signal, ideally deleting the slot object 
     m_WorkerConnection.disconnect(); 

     OnComplete(); 

     // the shared_ptr now goes out of scope, ideally deleting this 
    } 

    ActualWorker m_MyWorker; 
    boost::signals2::connection m_WorkerConnection; 
}; 

Lớp được gọi về như thế này:

... 
boost::shared_ptr<Command> cmd(new Command); 
cmd->OnComplete.connect(foo); 
cmd->Execute(); 
// now go do something else, forget all about the cmd variable etcetera. 

Lớp Lệnh giữ bản thân còn sống bằng cách nhận shared_ptr cho chính nó, nó được gắn với tín hiệu ActualWorker bằng cách sử dụng boost :: bind.

Khi nhân viên hoàn tất, trình xử lý trong Lệnh được gọi. Bây giờ, vì tôi muốn đối tượng Command bị phá hủy, tôi ngắt kết nối từ tín hiệu như có thể thấy trong đoạn mã trên. Vấn đề là đối tượng khe thực sự không bị xóa khi bị ngắt kết nối, nó chỉ được đánh dấu là không hợp lệ và sau đó bị xóa sau đó. Điều này lần lượt xuất hiện để phụ thuộc vào tín hiệu để bắn một lần nữa, mà nó không làm trong trường hợp của tôi, dẫn đến khe không bao giờ hết hạn. Đối tượng bind :: bind do đó không bao giờ đi ra khỏi phạm vi, giữ một shared_ptr với đối tượng của tôi mà sẽ không bao giờ bị xóa.

Tôi có thể giải quyết vấn đề này bằng cách sử dụng con trỏ này thay vì shared_ptr và sau đó giữ đối tượng của tôi còn sống bằng cách sử dụng thành viên shared_ptr mà sau đó tôi phát hành trong hàm xử lý, nhưng nó làm cho thiết kế cảm thấy hơi phức tạp. Có cách nào để buộc signal2 xóa khe khi ngắt kết nối không? Hoặc có cái gì khác tôi có thể làm để đơn giản hóa thiết kế?

Mọi nhận xét đều được đánh giá cao!

Trả lời

1

Tôi đã kết thúc việc thực hiện của riêng mình (tập hợp con) của một tín hiệu, yêu cầu chính là một khe sẽ bị phá hủy bởi một cuộc gọi đến kết nối :: disconnect().

Việc triển khai thực hiện dọc theo các dòng của tín hiệu lưu trữ tất cả các vị trí trong bản đồ từ con trỏ thực hiện vị trí đến shared_ptr cho việc triển khai vị trí thay vì danh sách/vectơ, do đó cho phép truy cập nhanh vào từng vùng riêng lẻ mà không phải lặp lại tất cả khe. Việc triển khai khe trong trường hợp của tôi về cơ bản là hàm boost ::.

Kết nối có weak_ptr với lớp triển khai bên trong cho tín hiệu và weak_ptr đến loại triển khai vị trí để cho phép tín hiệu vượt ra khỏi phạm vi và sử dụng con trỏ vị trí làm khóa vào bản đồ tín hiệu cũng như chỉ báo về việc liệu kết nối có còn hoạt động hay không (không thể sử dụng con trỏ thô vì có thể tái sử dụng).

Khi ngắt kết nối được gọi, cả hai con trỏ yếu này được chuyển thành shared_ptrs và nếu cả hai đều thành công, việc thực hiện tín hiệu được yêu cầu ngắt kết nối khe do con trỏ đưa ra. Điều này được thực hiện bằng cách xóa nó đơn giản khỏi bản đồ.

Bản đồ được bảo vệ bởi một mutex để cho phép sử dụng đa luồng. Để ngăn chặn deadlocks, các mutex không được tổ chức trong khi gọi các khe, tuy nhiên điều này có nghĩa là một khe có thể bị ngắt kết nối từ một sợi khác nhau ngay trước khi được gọi bởi tín hiệu. Đây cũng là trường hợp với boost thường xuyên :: signal2 và trong cả hai kịch bản này một trong những nhu cầu để có thể xử lý một cuộc gọi lại từ một tín hiệu ngay cả sau khi một đã bị ngắt kết nối.

Để đơn giản hóa mã khi tín hiệu được kích hoạt, tôi buộc tất cả các vị trí sẽ bị ngắt kết nối trong quá trình này. Điều này khác với boost :: signal2, một bản sao của danh sách các khe trước khi gọi chúng để xử lý các kết nối/ngắt kết nối trong khi kích hoạt tín hiệu.

Ở trên hoạt động tốt cho kịch bản của tôi, nơi tín hiệu phát sinh rất ít khi xảy ra (và trong trường hợp đó chỉ một lần) nhưng có rất nhiều kết nối ngắn ngủi mà sử dụng nhiều bộ nhớ ngay cả khi sử dụng mẹo được nêu trong câu hỏi.

Đối với các tình huống khác, tôi đã có thể thay thế việc sử dụng tín hiệu chỉ với chức năng tăng :: (do đó yêu cầu chỉ có thể có một kết nối) hoặc chỉ bằng cách gắn kết với giải pháp trong câu hỏi người nghe tự quản lý tuổi thọ của nó.

1

Hành vi có nghiêm ngặt hơn với scoped_connection không?

Vì vậy, chứ không phải là:

void Execute() { 
    m_WorkerConnection = m_MyWorker.OnWorkDone.connect(boost::bind 
     (&Command::Handle_OnWorkComplete, shared_from_this()); 

    // launch asynchronous work here and return 
} 

... 

boost::signals2::connection m_WorkerConnection; 

Thay vì sử dụng:

void Execute() { 
    boost::signals2::scoped_connection m_WorkerConnection 
     (m_MyWorker.OnWorkDone.connect(boost::bind 
     (&Command::Handle_OnWorkComplete, shared_from_this())); 

    // launch asynchronous work here and return 
} // connection falls out of scope 

(copy-xây dựng từ một boost::signals2::connection)

Tôi đã không được sử dụng bất kỳ loại tín hiệu do đó, nó hơn của một đoán hơn bất cứ điều gì khác, nhưng sau Execute() bạn sẽ không cần phải disconnect(), kể từ scoped_connection xử lý nó cho bạn. Đó là một 'đơn giản hóa thiết kế' hơn là thực sự giải quyết vấn đề của bạn. Nhưng điều đó có nghĩa là bạn có thể Execute() và sau đó ngay lập tức ~Command() (hoặc delete shared_ptr).

Hy vọng điều đó sẽ hữu ích.

CHỈNH SỬA: Và bằng cách Execute() thì ngay lập tức ~Command() Tôi rõ ràng là có nghĩa là từ bên ngoài đối tượng Lệnh của bạn. Khi bạn xây dựng Lệnh để thực thi nó, bạn sẽ có thể nói:

cmd->Execute(); 
delete cmd; 

Hoặc tương tự.

+0

Ngắt kết nối ở phần cuối của thực hiện chức năng đánh bại mục đích của việc kết nối ở tất cả - tức là tôi sẽ không nhận được một cuộc gọi lại khi không đồng bộ công việc đã hoàn thành. Vì vậy, đề xuất của bạn không thực sự khả thi. – villintehaspam

3

boost::signals2 sẽ dọn sạch các khe trong khi kết nối/gọi.

Vì vậy, nếu tất cả các khe tự ngắt kết nối khỏi tín hiệu, hãy gọi tín hiệu lần thứ hai sẽ không gọi bất cứ điều gì nhưng nó sẽ làm sạch các khe.

Để trả lời bình luận của bạn, có, gọi lại tín hiệu một lần nữa không an toàn nếu có các khe khác được kết nối, vì chúng sẽ được gọi lại. Trong trường hợp đó tôi đề nghị bạn đi theo cách khác và kết nối một khe giả, sau đó ngắt kết nối nó khi khe "thực" của bạn được gọi. Việc kết nối một khe khác sẽ làm sạch các kết nối cũ, vì vậy khe của bạn sẽ được giải phóng.

Chỉ cần đảm bảo rằng bạn không giữ bất kỳ tài liệu tham khảo nào cần giải phóng trong khe giả hoặc bạn quay lại nơi bạn đã bắt đầu.

+0

Đây chính xác là những gì tôi đã viết trong câu hỏi, mà tôi muốn tránh. Tôi xin lỗi nếu văn bản câu hỏi không truyền đạt điều này đúng cách. Dù sao, tín hiệu không được gọi lại - và tôi giả định rằng bạn không gợi ý gọi nó lần thứ hai như một phương tiện để xóa đối tượng? – villintehaspam

+0

Đó là chính xác những gì tôi đã đề xuất, xin lỗi vì những từ ngữ kỳ lạ. boost :: signal2 "garbage thu thập" các khe bị ngắt kết nối trong khi gọi, vì vậy nếu bạn gọi lại sau khi bạn ngắt kết nối, nó sẽ xóa đối tượng của bạn. –

+0

Xin lỗi vì đã trả lời trễ, không thể tiếp cận được một thời gian. Tuy nhiên, đề xuất của bạn sẽ yêu cầu tôi biết rằng không có khe cắm nào khác được kết nối với tín hiệu, nếu không, các yêu cầu này sẽ được gọi nhiều lần, có thể hoặc không an toàn. – villintehaspam

2

Đây là khía cạnh cực kỳ khó chịu khi tăng :: signal2.

Cách tiếp cận tôi thực hiện để giải quyết nó là lưu tín hiệu trong phạm vi scoped_ptr và khi tôi muốn ngắt kết nối tất cả các vị trí, tôi sẽ xóa tín hiệu. Điều này chỉ hoạt động trong trường hợp khi bạn muốn ngắt kết nối tất cả các kết nối với tín hiệu một cách mạnh mẽ.

1

Tôi tình cờ gặp vấn đề tương tự và tôi thực sự bỏ lỡ một số loại dọn dẹp rõ ràng trong API.

Trong trường hợp của tôi, tôi đang tải xuống một số dll của trình cắm thêm và tôi phải đảm bảo không có các đối tượng lơ lửng (các khe) tham chiếu đến mã (vftables hoặc bất kỳ thứ gì) sống trong dll chưa tải. Chỉ cần ngắt kết nối các vị trí không hoạt động do công cụ xóa nội dung bị xóa.

workaround đầu tiên của tôi là một wrapper tín hiệu mà tweaks mã ngắt kết nối một chút:

template <typename Signature> 
struct MySignal 
{ 
    // ... 

    template <typename Slot> 
    void disconnect (Slot&& s) 
    { 
    mPrivate.disconnect (forward (s)); 
    // connect/disconnect dummy slot to force cleanup of s 
    mPrivate.connect (&MySignal::foo); 
    mPrivate.disconnect (&MySignal::foo); 
    } 

private: 
    // dummy slot function with matching signature 
    // ... foo (...) 

private: 
    ::boost::signals2::signal<Signature> mPrivate; 
}; 

Thật không may điều này không làm việc vì connect() chỉ làm một số dọn dẹp. Nó không đảm bảo dọn dẹp tất cả các khe chưa được kết nối. Lời gọi tín hiệu mặt khác làm sạch hoàn toàn nhưng một lời gọi giả cũng sẽ là một thay đổi hành vi không được chấp nhận (như đã được đề cập bởi những người khác).

Trong trường hợp không lựa chọn thay thế tôi đã kết thúc trong vá signal lớp gốc (Edit:. i thực sự sẽ đánh giá cao một giải pháp tích hợp bản vá này là phương sách cuối cùng của tôi). Bản vá của tôi có khoảng 10 dòng mã và thêm phương thức công khai cleanup_connections() vào signal. Trình bao bọc tín hiệu của tôi gọi sự dọn dẹp ở cuối các phương thức ngắt kết nối. Cách tiếp cận này giải quyết vấn đề của tôi và tôi đã không gặp phải bất kỳ vấn đề hiệu suất cho đến nay.

Edit: Đây là bản vá của tôi cho tăng 1.5.3

Index: signals2/detail/signal_template.hpp 
=================================================================== 
--- signals2/detail/signal_template.hpp 
+++ signals2/detail/signal_template.hpp 
@@ -220,6 +220,15 @@ 
      typedef mpl::bool_<(is_convertible<T, group_type>::value)> is_group; 
      do_disconnect(slot, is_group()); 
     } 
+  void cleanup_connections() const 
+  { 
+   unique_lock<mutex_type> list_lock(_mutex); 
+   if(_shared_state.unique() == false) 
+   { 
+   _shared_state.reset(new invocation_state(*_shared_state, _shared_state->connection_bodies())); 
+   } 
+   nolock_cleanup_connections_from(false, _shared_state->connection_bodies().begin()); 
+  } 
     // emit signal 
     result_type operator()(BOOST_SIGNALS2_SIGNATURE_FULL_ARGS(BOOST_SIGNALS2_NUM_ARGS)) 
     { 
@@ -690,6 +699,10 @@ 
     { 
     (*_pimpl).disconnect(slot); 
     } 
+  void cleanup_connections() 
+  { 
+  (*_pimpl).cleanup_connections(); 
+  } 
     result_type operator()(BOOST_SIGNALS2_SIGNATURE_FULL_ARGS(BOOST_SIGNALS2_NUM_ARGS)) 
     { 
     return (*_pimpl)(BOOST_SIGNALS2_SIGNATURE_ARG_NAMES(BOOST_SIGNALS2_NUM_ARGS)); 
Các vấn đề liên quan