2016-10-26 19 views
5

Tôi có mã sau (kết quả từ my previous question) lên lịch tác vụ trên máy chủ từ xa và sau đó thăm dò để hoàn thành sử dụng ScheduledExecutorService#scheduleAtFixedRate. Khi tác vụ hoàn tất, nó sẽ tải xuống kết quả. Tôi muốn trả lại số Future cho người gọi để họ có thể quyết định thời gian và thời gian chặn và cung cấp cho họ tùy chọn hủy tác vụ.CompletableFuture # whenComplete không được gọi nếu sau đóApply được sử dụng

Vấn đề của tôi là nếu khách hàng hủy bỏ Future được trả về theo phương pháp download, thì khối whenComplete không thực thi. Nếu tôi xóa thenApply. Rõ ràng là tôi hiểu nhầm điều gì đó về thành phần Future ... Tôi nên thay đổi điều gì?

public Future<Object> download(Something something) { 
    String jobId = schedule(something); 
    CompletableFuture<String> job = pollForCompletion(jobId); 
    return job.thenApply(this::downloadResult); 
} 

private CompletableFuture<String> pollForCompletion(String jobId) { 
    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); 
    CompletableFuture<String> completionFuture = new CompletableFuture<>(); 

    ScheduledFuture<?> checkFuture = executor.scheduleAtFixedRate(() -> {   
      if (pollRemoteServer(jobId).equals("COMPLETE")) { 
       completionFuture.complete(jobId); 
      } 
    }, 0, 10, TimeUnit.SECONDS); 
    completionFuture     
      .whenComplete((result, thrown) -> { 
       System.out.println("XXXXXXXXXXX"); //Never happens unless thenApply is removed 
       checkFuture.cancel(true); 
       executor.shutdown(); 
      }); 
    return completionFuture; 
} 

Cùng lưu ý, nếu tôi làm:

return completionFuture.whenComplete(...) 

thay vì

completionFuture.whenComplete(...); 
return completionFuture; 

whenComplete cũng không bao giờ được thực thi. Điều này có vẻ rất phản đối với tôi. Không nên hợp lý Future trả lại bởi whenComplete là một trong những tôi nên giữ?

EDIT:

tôi đã thay đổi mã của tôi để rõ ràng back-tuyên truyền việc hủy bỏ. Đó là đáng ghê tởm và không thể đọc được, nhưng nó hoạt động và tôi không thể tìm thấy một cách tốt hơn:

public Future<Object> download(Something something) throws ChartDataGenException, Exception { 
     String jobId = schedule(report); 
     CompletableFuture<String> job = pollForCompletion(jobId); 
     CompletableFuture<Object> resulting = job.thenApply(this::download); 
     resulting.whenComplete((result, thrown) -> { 
      if (resulting.isCancelled()) { //the check is not necessary, but communicates the intent better 
       job.cancel(true); 
      } 
     }); 
     return resulting; 
} 
+0

Nó thậm chí không nhập khối 'whenComplete'. Tôi đặt một điểm ngắt và một 'System.out.print' bên trong, và cũng không có breakpoint được nhấn cũng không dòng được in. Cả hai xảy ra nếu tôi xóa bit 'thenApply'. – kaqqao

+0

Theo sự hiểu biết của tôi, nó phải ngược lại những gì bạn báo cáo. 'completionFuture.whenComplete()' là một hàm thuần túy và không nên thay đổi bất cứ thứ gì theo cách 'completionFuture' tự hoạt động. Nếu bạn không trả lại kết quả của 'whenComplete', nó sẽ trở thành không thể truy cập và phải tuân theo GC. –

+0

Hoàn toàn đồng ý. Nhưng đây là những gì tôi nhìn thấy ... Nếu trả về kết quả của 'whenComplete', và' hủy' nó ngay lập tức, tôi không nhận được XXXXX trong giao diện điều khiển. Mặt khác, nếu tôi trả lại 'completeFuture' ban đầu và' cancel' _that_, tôi làm. – kaqqao

Trả lời

4

cấu trúc của bạn là như sau:

  ┌──────────────────┐ 
      │ completionFuture | 
      └──────────────────┘ 
      ↓    ↓ 
    ┌──────────────┐  ┌───────────┐ 
    │ whenComplete |  │ thenApply | 
    └──────────────┘  └───────────┘ 

Vì vậy, khi bạn hủy tương lai thenApply, các completionFuture đối tượng ban đầu vẫn không bị ảnh hưởng vì nó không phụ thuộc vào giai đoạn thenApply. Tuy nhiên, nếu bạn không kết nối giai đoạn thenApply, bạn sẽ trả về phiên bản completionFuture gốc và hủy giai đoạn này sẽ hủy bỏ tất cả các giai đoạn phụ thuộc, khiến hành động whenComplete được thực hiện ngay lập tức.

Nhưng khi giai đoạn thenApply bị hủy, completionFuture vẫn có thể được hoàn thành khi điều kiện pollRemoteServer(jobId).equals("COMPLETE") được hoàn thành, vì cuộc thăm dò đó không dừng lại. Nhưng chúng tôi không biết mối quan hệ của jobId = schedule(something)pollRemoteServer(jobId). Nếu ứng dụng của bạn thay đổi trạng thái trong một cách mà tình trạng này có thể không bao giờ được hoàn thành sau khi huỷ một tải về, tương lai này sẽ không bao giờ hoàn chỉnh ...


Về câu hỏi cuối cùng của bạn, mà tương lai là “một trong những tôi nên giữ cho? ", Không có yêu cầu phải có một chuỗi tương lai tuyến tính, trên thực tế, trong khi các phương pháp tiện lợi của CompletableFuture giúp dễ dàng tạo chuỗi như vậy, thường xuyên hơn, đó là điều hữu ích nhất để làm, vì bạn chỉ cần viết khối mã, nếu bạn có một phụ thuộc tuyến tính. Mô hình chuỗi hai giai đoạn độc lập của bạn là đúng, nhưng việc hủy không hoạt động thông qua nó, nhưng nó cũng không hoạt động thông qua một chuỗi tuyến tính.

Nếu bạn muốn có thể hủy giai đoạn nguồn, bạn cần tham chiếu đến giai đoạn nguồn, nhưng nếu bạn muốn có được kết quả của giai đoạn phụ thuộc, bạn cũng sẽ cần tham chiếu đến giai đoạn đó.

+0

OP không cần kết quả của 'whenComplete'. Nhưng sự hiểu biết của tôi là giai đoạn này phải được giữ lại một cách rõ ràng, nếu không nó không thể truy cập được và có thể hoặc có thể không tồn tại tại thời điểm hủy bỏ. –

+0

@MarkoTopolnik Tôi đoán tương lai ban đầu mà bạn gọi là 'whenComplete' trên vẫn giữ nguyên tham chiếu đến chuỗi bị xích ... Nếu không, nó sẽ rất nghiêm trọng. – kaqqao

+0

@kaqqao Có thể là do cách người ta hy vọng điều này sẽ được thực hiện, nhưng nó vẫn là hành vi không xác định và không lành mạnh để dựa vào. –

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