2013-03-14 32 views
7

Tôi có trường hợp sử dụng đến từ một vấn đề GUI mà tôi muốn gửi cho sự sagacity của bạn.Mẫu đơn không đồng bộ thông minh trong Java

Sử dụng trường hợp

Tôi có một giao diện hiển thị kết quả tính toán phụ thuộc vào một số thông số người dùng thiết lập trong một GUI. Ví dụ: khi người dùng di chuyển một thanh trượt, một số sự kiện được kích hoạt, tất cả sẽ kích hoạt tính toán mới. Khi người dùng điều chỉnh giá trị thanh trượt từ A đến B, hàng chục sự kiện sẽ được kích hoạt.

Nhưng tính toán có thể mất đến vài giây, trong khi điều chỉnh thanh trượt có thể kích hoạt sự kiện cứ sau 100 ms.

Làm thế nào để viết một chủ đề thích hợp mà có thể lắng nghe những sự kiện này, và loại lọc chúng để các repaint của kết quả là sống động? Lý tưởng nhất là bạn muốn một cái gì đó như

  • bắt đầu một tính toán mới ngay khi nhận được sự kiện thay đổi đầu tiên;
  • hủy tính toán đầu tiên nếu một sự kiện mới được nhận và bắt đầu một sự kiện mới với các thông số mới;
  • nhưng đảm bảo rằng sự kiện cuối cùng sẽ không bị mất, vì tính toán hoàn thành cuối cùng cần phải là sự kiện có thông số được cập nhật lần cuối.

Những gì tôi đã cố gắng

Một người bạn của tôi (A. Cardona) đề xuất phương pháp này ở mức độ thấp của một chủ đề Updater có thể ngăn chặn quá nhiều sự kiện để kích hoạt một tính toán. Tôi sao chép-dán nó ở đây (GPL):

Ông đặt này trong một lớp học kéo dài Ðề tài:

public void doUpdate() { 
    if (isInterrupted()) 
     return; 
    synchronized (this) { 
     request++; 
     notify(); 
    } 
} 

public void quit() { 
    interrupt(); 
    synchronized (this) { 
     notify(); 
    } 
} 

public void run() { 
    while (!isInterrupted()) { 
     try { 
      final long r; 
      synchronized (this) { 
       r = request; 
      } 
      // Call refreshable update from this thread 
      if (r > 0) 
       refresh(); // Will trigger re-computation 
      synchronized (this) { 
       if (r == request) { 
        request = 0; // reset 
        wait(); 
       } 
       // else loop through to update again 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 


public void refresh() { 
    // Execute computation and paint it 
    ... 
} 

Mỗi lần một sự kiện được gửi bởi GUI nói rằng thông số đã được thay đổi, chúng ta gọi là updater.doUpdate(). Điều này gây ra phương pháp refresh() được gọi là ít hơn nhiều. Nhưng tôi không kiểm soát được điều này.

Một cách khác?

Tôi đã tự hỏi nếu có một cách khác để làm điều đó, điều đó sẽ sử dụng các lớp jaca.concurrent. Nhưng tôi không thể phân loại trong khung công tác Executors thì tôi nên bắt đầu với cái gì.

Có ai trong số các bạn có kinh nghiệm với một ca sử dụng tương tự không?

Cảm ơn

Trả lời

4

Nếu bạn đang sử dụng Swing, SwingWorker cung cấp khả năng cho việc này và bạn không phải tự xử lý với nhóm chủ đề.

cháy ra một SwingWorker cho mỗi yêu cầu. Nếu một yêu cầu mới đến và công nhân không được thực hiện, bạn có thể cancel() nó và chỉ cần bắt đầu một SwingWorker mới. Về những gì người khác nói, tôi không nghĩ rằng publish()process() là những gì bạn đang tìm kiếm (mặc dù chúng cũng rất hữu ích), vì chúng có nghĩa là trường hợp nhân viên có thể kích hoạt các sự kiện nhanh hơn GUI nó.

ThingyWorker worker; 

public void actionPerformed(ActionEvent e) { 
    if(worker != null) worker.cancel(); 
    worker = new ThingyWorker(); 
    worker.execute(); 
} 

class ThingyWorker extends SwingWorker<YOURCLASS, Object> { 
    @Override protected YOURCLASS doInBackground() throws Exception { 
     return doSomeComputation(); // Should be interruptible 
    } 
    @Override protected void done() { 
     worker = null; // Reset the reference to worker 

     YOURCLASS data; 

     try { 
      data = get(); 
     } catch (Exception e) { 
      // May be InterruptedException or ExecutionException     
      e.printStackTrace(); 
      return; 
     }   

     // Do something with data 
    }  
} 

Cả hai hành động và done() phương pháp được thực hiện trên cùng một sợi, vì vậy họ có thể kiểm tra một cách hiệu quả các tài liệu tham khảo về việc liệu có một nhân viên hiện có.

Lưu ý rằng hiệu quả này đang làm điều tương tự cho phép một giao diện để hủy bỏ một hoạt động hiện có, ngoại trừ hủy được thực hiện tự động khi một yêu cầu mới bị sa thải.

+0

giải pháp tốt. nhưng nó không rõ ràng với tôi cho dù có một cơ hội nhỏ mà sau khi 'worker1.cancel()', 'worker1.done()' vẫn được thực hiện, và nó có thể được thực hiện sau 'worker2.done()', tức là một kết quả cũ sẽ ghi đè lên một kết quả mới hơn. cần điều tra thêm một chút ở đây. – ZhongYu

+0

Điều này không giải quyết được vấn đề làm thế nào để lô các yêu cầu, để tránh tình huống mà bạn đang hủy bỏ và khởi động lại các chủ đề nhiều lần trong một thời gian ngắn. –

+0

Tôi không nghĩ rằng bạn có thể tránh việc hủy bỏ các yêu cầu, bởi vì bạn không bao giờ biết nếu một số yêu cầu là cuối cùng. Chắc chắn, bạn có thể chờ một chút trước khi sa thải nhân viên, để xem liệu có nhiều yêu cầu hơn không, nhưng điều đó sẽ chỉ làm cho bản cập nhật chậm hơn. –

1

Tôi sẽ cung cấp thêm mức độ ngắt kết nối giữa GUI và điều khiển bằng cách sử dụng hàng đợi.

Nếu bạn sử dụng BlockingQueue giữa hai quy trình. Bất cứ khi nào các điều khiển thay đổi, bạn có thể đăng các cài đặt mới lên hàng đợi.

Thành phần đồ họa của bạn có thể đọc hàng đợi bất cứ khi nào nó thích và hành động trên các sự kiện đến hoặc loại bỏ chúng khi cần thiết.

1

Tôi sẽ xem xét SwingWorker.publish() (http://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html)

Xuất bản cho phép chuỗi nền của đối tượng SwingWorker thực hiện cuộc gọi đến phương thức process(), nhưng không phải mọi kết quả cuộc gọi publish() trong một cuộc gọi process(). Nếu nhiều cuộc gọi quá trình được thực hiện trước khi process() trả về và có thể được gọi lại, SwingWorker nối các thông số được sử dụng cho nhiều lần xuất bản cuộc gọi vào một cuộc gọi để xử lý.

Tôi đã có hộp thoại tiến trình hiển thị các tệp đang được xử lý; các tệp đã được xử lý nhanh hơn giao diện người dùng có thể theo kịp chúng và tôi không muốn quá trình xử lý làm chậm để hiển thị tên tệp; Tôi đã sử dụng điều này và đã có quá trình chỉ hiển thị tên tệp cuối cùng được gửi tới process(); tất cả những gì tôi muốn trong trường hợp này là để chỉ ra cho người dùng biết nơi xử lý hiện tại là, họ sẽ không đọc tất cả các tên tệp. Giao diện người dùng của tôi hoạt động rất suôn sẻ với điều này.

+0

Cảm ơn! Nó sẽ được áp dụng cho một cái gì đó khác hơn là một GUI? –

+1

Tôi không nghĩ rằng 'process' và' publish' là những gì OP đang tìm kiếm. Bạn không thể cập nhật một 'SwingWorker' với các sự kiện mới. Trong trường hợp này, vấn đề là khác: xử lý chậm hơn các hành động được kích hoạt bởi giao diện người dùng. –

+0

@AndrewMao điểm tốt - xuất bản/quá trình đang cập nhật giao diện người dùng do xử lý và sự cố này bao gồm việc cập nhật xử lý từ giao diện người dùng. Các sự kiện do UI tạo ra đã được xếp hàng đợi, tất nhiên; có lẽ chương trình có thể nhận các sự kiện trượt cho đến khi không còn sự kiện nào trong hàng đợi, sau đó bắt đầu xử lý trên giá trị gần đây nhất vì các giá trị trước đó không còn phù hợp nữa. Vì vậy, tôi nghĩ ý tưởng của tôi có liên quan đến việc cập nhật giao diện người dùng, nhưng bạn nói đúng là có vấn đề về xử lý giao diện người dùng cần xem xét riêng. – arcy

1

Hãy xem việc thực hiện javax.swing.SwingWorker (mã nguồn trong Java JDK), với một tập trung vào bắt tay giữa hai phương pháp: xuất bảnquá trình.

Điều này sẽ không được áp dụng trực tiếp, đúng như vấn đề của bạn - tuy nhiên chúng thể hiện cách bạn có thể xếp hàng (xuất bản) cập nhật lên chuỗi công nhân và sau đó phục vụ chúng trong luồng công việc (quy trình).

Vì bạn chỉ cần yêu cầu công việc cuối cùng, bạn thậm chí không cần một hàng đợi cho tình hình của bạn: giữ chỉ yêu cầu công việc cuối cùng. Lấy mẫu "yêu cầu cuối cùng" trong một khoảng thời gian nhỏ (1 giây), để tránh dừng/khởi động lại nhiều lần mỗi 1 giây, và nếu nó thay đổi THEN dừng công việc và khởi động lại.


Lý do bạn không muốn sử dụng xuất bản/quá trình như nó vốn có là quá trình luôn chạy trên Swing văn Event Chủ đề - không phải ở tất cả phù hợp cho các tính toán chạy dài.

+0

Điều này phụ thuộc vào cách anh ta sử dụng nó - nếu tất cả các quá trình làm là cập nhật giao diện người dùng, đó là chính xác những gì anh ta muốn. Anh ấy không phải (và không nên) làm phép tính dài của anh ấy ở đó, tôi đồng ý. Nhưng nó là chính xác cho một phần của vấn đề của mình. – arcy

0

Chìa khóa ở đây là bạn muốn có thể hủy tính toán liên tục. Tính toán phải thường xuyên kiểm tra một điều kiện để xem nó có cần phải hủy bỏ hay không.

volatile Param newParam; 

Result compute(Param param) 
{ 
    loop 
     compute a small sub problem 
     if(newParam!=null) // abort 
      return null; 

    return result 
} 

Để bàn giao param từ chủ đề sự kiện để tính chủ đề

synchronized void put(Param param) // invoked by event thread 
    newParam = param; 
    notify(); 

synchronized Param take() 
    while(newParam==null) 
     wait(); 
    Param param = newParam; 
    newParam=null; 
    return param; 

Và thread tính toán làm

public void run() 
    while(true) 
     Param param = take(); 
     Result result = compute(param); 
     if(result!=null) 
      paint result in event thread 
+0

Tôi không biết về 'thường xuyên kiểm tra một điều kiện', hay còn gọi là bỏ phiếu; đây là những gì bị gián đoạn. –

+0

làm thế nào để bạn làm gián đoạn một vòng lặp bận rộn :?) – ZhongYu

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