2012-02-08 13 views
6

Tôi có luồng dữ liệu này, khoảng:Java + Swing: viết code lại với nhau các sự kiện thay đổi

DataGenerator -> DataFormatter -> UI 

DataGenerator là một cái gì đó mà tạo ra dữ liệu nhanh chóng; DataFormatter là một cái gì đó định dạng nó cho mục đích hiển thị; và giao diện người dùng chỉ là một loạt các yếu tố Swing.

Tôi muốn làm một cái gì đó DataGenerator của tôi như thế này:

class DataGenerator 
{ 
    final private PropertyChangeSupport pcs; 
    ... 
    public void addPropertyChangeListener(PropertyChangeListener pcl) { 
    this.pcs.addPropertyChangeListener(pcl); 
    } 
    public void removePropertyChangeListener(PropertyChangeListener pcl) { 
    this.pcs.removePropertyChangeListener(pcl); 
    } 
} 

và chỉ cần gọi this.pcs.firePropertyChange(...) bất cứ khi nào phát dữ liệu của tôi có dữ liệu mới; sau đó tôi chỉ có thể làm dataGenerator.addPropertyListener(listener) trong đó listener chịu trách nhiệm đẩy thay đổi chuyển tiếp đến DataFormatter và sau đó đến giao diện người dùng.

Vấn đề với cách tiếp cận này là có hàng nghìn thay đổi DataGenerator mỗi giây (từ 10.000 đến 60.000 mỗi giây tùy thuộc vào tình huống của tôi) và chi phí tính toán định dạng cho giao diện người dùng đủ cao tải không cần thiết trên CPU của tôi; thực sự tất cả tôi quan tâm về trực quan là nhiều nhất 10-20 thay đổi mỗi giây.

Có cách nào để sử dụng cách tiếp cận tương tự, nhưng kết hợp các sự kiện thay đổi trước khi chúng đến DataFormatter không? Nếu tôi nhận được nhiều sự kiện cập nhật trên một chủ đề, tôi chỉ quan tâm đến việc hiển thị sự kiện mới nhất và có thể bỏ qua tất cả các chủ đề trước đó.

+0

Cách duy trì giá trị dài với System.nanoTime của giao diện người dùng cuối cùng- cập nhật và bỏ qua các sự kiện thay đổi thuộc tính nếu chúng xảy ra N ns sau khi cập nhật? – aioobe

+0

Tôi nghĩ rằng (heck, 'System.currentTimeMillis()' sẽ làm việc - Tôi chỉ quan tâm nếu mọi thứ nhanh chóng liên quan đến nhận thức của con người), nhưng có một vấn đề. Giả sử bạn có sự kiện thay đổi 100 micro giây sau khi cập nhật trực quan, vì vậy DataFormatter/UI bỏ qua thay đổi. Bây giờ vì lý do nào đó DataGenerator ngừng sản xuất các bản cập nhật (nó không nhất thiết phải liên tục 10-60K sự kiện mỗi giây). Rất tiếc, bạn đã bỏ lỡ thay đổi gần đây nhất. Thật tồi tệ. –

Trả lời

4

Hai ý tưởng:

  • tổng hợp PropertyChangeEvent s. Mở rộng PropertyChangeSupport, ghi đè public void firePropertyChange(PropertyChangeEvent evt), chỉ kích hoạt nếu sự kiện cuối cùng được kích hoạt hơn 50 mili giây (hoặc bất kỳ thời điểm nào có vẻ thích hợp) trước đây. (Trong thực tế, bạn nên ghi đè mọi phương pháp fire* hoặc ít nhất là phương pháp bạn sử dụng trong kịch bản của mình để ngăn chặn việc tạo PropertyChangeEvent.)
  • Thả toàn bộ sự kiện được tiếp cận. 60.000 sự kiện mỗi giây là một con số khá cao. Trong tình huống này tôi sẽ thăm dò ý kiến. Nó là một thay đổi khái niệm cho MVP, nơi người trình bày biết nếu nó đang ở trạng thái hoạt động và nên thăm dò ý kiến. Với cách tiếp cận này, bạn không tạo ra hàng nghìn sự kiện vô dụng; bạn thậm chí có thể trình bày các khung hình cao nhất có thể mỗi giây, bất kể có bao nhiêu dữ liệu. Hoặc bạn có thể đặt người trình bày thành một tỷ lệ cố định, để cho nó ngủ giữa các lần làm mới trong một thời gian nhất định, hoặc để nó thích ứng với các tình huống khác (như tải CPU).

Tôi có xu hướng tiếp cận thứ hai.

+0

OK. Đối với phương pháp tiếp cận thứ hai của bạn, có cách nào để sử dụng bỏ phiếu (tôi có thể dễ dàng giữ một "thay đổi" cờ của một số loại bằng cách sử dụng AtomicBoolean) nhưng vẫn giữ một tách giữa hai bên? Tôi không muốn DataGenerator của mình phải biết về máy khách hạ lưu của nó; hơn nữa, tuyên bố vấn đề của tôi đã được đơn giản hóa một chút: trong thực tế có 256 DataGenerators với tổng cộng 10-60K thay đổi mỗi giây. (phần lớn thời gian chỉ có một vài người hoạt động, nhưng tôi không thể dự đoán cái nào đang ở bất kỳ thời điểm nào. Đó là một câu chuyện dài.) Cách tiếp cận thay đổi đặc tính là tốt khi tôi được thông báo khi cần thiết. –

+0

Nhưng bạn vẫn phải đăng ký tại một số trường hợp ngay từ đầu. Có vấn đề gì nếu bạn đang lưu trữ tham chiếu cho việc bỏ phiếu hoặc gọi 'reference.addPropertyChangeListener (this)'? Tôi đồng ý rằng nó có thể là một khái niệm sạch hơn để có một mô hình hoạt động, tách rời - nhưng không phải với số lượng dữ liệu này (và mô hình MVP thay đổi trách nhiệm vì những lý do đó). Tôi sẽ viết một 'Aggregator' biết' DataGenerators' của bạn, thu thập dữ liệu và có thể được thăm dò bởi người trình bày. Trong thực tế, 'Aggregator' này thậm chí có thể gửi 20 sự kiện mỗi giây. Tôi chỉ không thấy 60.000 sự kiện xử lý hiệu quả hoặc hữu ích. –

+0

Ah - Tôi thích ý tưởng tổng hợp, cảm ơn! –

0

Nếu bạn viết riêng người nghe sự thay đổi nhỏ của bạn có một lá cờ chặn và một bộ đếm thời gian, bạn có thể:

syncronized onChangeRequest() { 
    if (flag) { 
     flag = false; 
     startTimer(); 
    } 
} 

timerEvent() { 
    notify all your listeners; 
} 

Tôi tin rằng có thực sự là một chặn tuyệt vời đồng thời cờ mà có thể được sử dụng nhưng tôi không thể cho cuộc sống của tôi nhớ những gì nó được gọi là!

1

Bạn có thể sử dụng một ArrayBlockingQueue kích thước 1, trong đó bạn sẽ đẩy dữ liệu của bạn với offer() chức năng (có nghĩa là nếu hàng đợi đầy, nó không làm gì)

Sau đó tạo một ScheduledThreadPoolExecutor mà các cuộc thăm dò định kỳ hàng đợi.

Bằng cách này, bạn mất liên kết giữa thế hệ và màn hình.

Generator -> Queue -> Formatting/Hiển thị

+0

Nhưng tôi không muốn một hàng đợi; Tôi muốn kết hợp các sự kiện.Nếu một DataGenerator nói "Giá trị của tôi là màu xanh!" và sau đó "Giá trị của tôi là màu đỏ!" và sau đó "Giá trị của tôi là màu tím!" Tôi không quan tâm đến hai cái đầu tiên, tôi chỉ quan tâm đến cái mới nhất. –

3

Nghe có vẻ như bạn DataGenerator làm rất nhiều công việc phi GUI trên EDT sợi. Tôi khuyên bạn nên DataGenerator mở rộng SwingWorker và bằng cách thực hiện công việc đó trong chuỗi nền, được triển khai trong doInBackground. Sau đó, SwingWorker có thể publish kết quả trung gian tới GUI, trong khi bạn có phương thức process nhận được một vài khối đã xuất bản cuối cùng trên EDT và cập nhật GUI của bạn.

Các SwingWorkers process phương pháp làm coalesce các published khối, vì vậy nó sẽ không chạy một lần cho tất cả các kết quả trung gian công bố.

Nếu bạn chỉ quan tâm đến kết quả cuối cùng trên EDT bạn có thể sử dụng mã này, mà chỉ quan tâm đến đoạn cuối cùng trong danh sách:

@Override 
protected void process(List<Integer> chunks) { 

    // get the *last* chunk, skip the others 
    doSomethingWith(chunks.get(chunks.size() - 1)); 
} 

đọc thêm về SwingWorker: Tasks that Have Interim Results.

+0

DataGenerator không nằm trong chuỗi EDT và vì nhiều lý do khác nhau xảy ra trong một chuỗi khác được quản lý bởi ExecutorService. –

+0

@ Jason: Tôi hiểu rồi. Nhưng 'SwingWorker' cũng có thể được chạy trên một' ExecutorService', xem tài liệu 'SwingWorker':' Bởi vì SwingWorker thực hiện Runnable, một SwingWorker có thể được gửi đến một Executor để thực thi.' – Jonas

+0

@JasonS: Tôi có một ví dụ đầy đủ về cách sử dụng kết quả trung gian mới nhất từ ​​một SwingWorker trong câu trả lời của tôi cho [Stop/cancel SwingWorker thread?] (http://stackoverflow.com/a/9182043/213269) – Jonas

1

Một khả năng khác là chỉ cần thêm người nghe vào máy phát của bạn, nhưng thay vì phản ứng trực tiếp với thay đổi, bạn chỉ cần khởi động bộ hẹn giờ. Vì vậy, người nghe của bạn sẽ trông như thế (trong loại pseudo-code vì tôi quá lười biếng để cháy lên IDE của tôi hay nhìn lên chữ ký phương pháp chính xác)

Timer timer = new Timer(100, new ActionListener(){//update the UI in this listener}; 

public void propertyChange(PropertyChangeEvent event){ 
if (timer.isRunning()){ 
    timer.restart(); 
} else { 
    timer.start(); 
} 
} 

này sẽ làm việc trừ khi máy phát dữ liệu của bạn vẫn tiếp tục tạo ra dữ liệu toàn bộ thời gian, hoặc nếu bạn muốn cập nhật trung gian là tốt. Trong trường hợp đó, bạn chỉ có thể xóa cuộc gọi timer.restart() hoặc chọn bất kỳ đề xuất nào khác trong chuỗi này (cơ chế bỏ phiếu hoặc SwingWorker)

Các vấn đề liên quan