5

Tôi có đoạn mã sau:Ném ngoại lệ từ CompletableFuture

// How to throw the ServerException? 
public void myFunc() throws ServerException{ 
    // Some code 
    CompletableFuture<A> a = CompletableFuture.supplyAsync(() -> { 
     try { 
      return someObj.someFunc(); 
     } catch(ServerException ex) { 
      // throw ex; gives an error here. 
     } 
    })); 
    // Some code 
} 

someFunc() ném một ServerException. Tôi không muốn xử lý điều này ở đây nhưng hãy loại trừ ngoại lệ từ someFunc() đến người gọi myFunc().

Trả lời

11

Mã của bạn cho thấy rằng bạn đang sử dụng kết quả của hoạt động không đồng bộ sau này trong cùng một phương pháp, do đó bạn sẽ phải đối phó với CompletionException anyway, do đó một cách để đối phó với nó, là

public void myFunc() throws ServerException { 
    // Some code 
    CompletableFuture<A> a = CompletableFuture.supplyAsync(() -> { 
     try { return someObj.someFunc(); } 
     catch(ServerException ex) { throw new CompletionException(ex); } 
    }); 
    // Some code running in parallel to someFunc() 

    A resultOfA; 
    try { 
     resultOfA = a.join(); 
    } 
    catch(CompletionException ex) { 
     try { 
      throw ex.getCause(); 
     } 
     catch(Error|RuntimeException|ServerException possible) { 
      throw possible; 
     } 
     catch(Throwable impossible) { 
      throw new AssertionError(impossible); 
     } 
    } 
    // some code using resultOfA 
} 

Tất cả các trường hợp ngoại lệ ném ra bên trong quá trình xử lý không đồng bộ của Supplier sẽ nhận được gói gọn trong một CompletionException khi gọi join, ngoại trừ ServerException chúng tôi đã gói trong một CompletionException.

Khi chúng ta lại ném nguyên nhân của CompletionException, chúng ta có thể gặp phải trường hợp ngoại lệ được kiểm soát, ví dụ: lớp con của Error hoặc RuntimeException, hoặc tùy chỉnh của chúng tôi kiểm tra ngoại lệ ServerException. Mã trên xử lý tất cả chúng với một đa bắt mà sẽ ném lại chúng. Vì kiểu trả về được khai báo là getCause()Throwable, trình biên dịch yêu cầu chúng tôi xử lý loại đó mặc dù chúng tôi đã xử lý tất cả các loại có thể. Các giải pháp thẳng về phía trước là để ném này thực sự không thể ném được bọc trong một AssertionError.

Ngoài ra, chúng ta có thể sử dụng một tương lai kết quả thay thế cho ngoại lệ tùy chỉnh của chúng tôi:

public void myFunc() throws ServerException { 
    // Some code 
    CompletableFuture<ServerException> exception = new CompletableFuture<>(); 
    CompletableFuture<A> a = CompletableFuture.supplyAsync(() -> { 
     try { return someObj.someFunc(); } 
     catch(ServerException ex) { 
      exception.complete(ex); 
      throw new CompletionException(ex); 
     } 
    }); 
    // Some code running in parallel to someFunc() 

    A resultOfA; 
    try { 
     resultOfA = a.join(); 
    } 
    catch(CompletionException ex) { 
     if(exception.isDone()) throw exception.join(); 
     throw ex; 
    } 

    // some code using resultOfA 
} 

Giải pháp này sẽ tái ném tất cả throwables “bất ngờ” ở dạng bọc của họ, nhưng chỉ ném tục ServerException trong ban đầu của nó biểu mẫu được chuyển qua tương lai exception. Lưu ý rằng chúng tôi phải đảm bảo rằng a đã được hoàn tất (như gọi số join() trước), trước khi chúng tôi truy vấn tương lai exception, để tránh điều kiện chủng tộc.

+0

điều này là rất tốt đẹp ... – Eugene

+0

câu trả lời rất chi tiết. –

2

Tôi nghĩ rằng bạn nên quấn đó vào một RuntimeException và ném rằng:

throw new RuntimeException(ex); 

Hoặc nhiều được một tiện ích nhỏ sẽ giúp:

static class Wrapper extends RuntimeException { 

    private Wrapper(Throwable throwable) { 
     super(throwable); 
    } 

    public static Wrapper wrap(Throwable throwable) { 
     return new Wrapper(throwable); 
    } 

    public Throwable unwrap() { 
     return getCause(); 
    } 
} 


public static void go() { 
    CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> { 
     try { 
      throw new Exception("Just because"); 
     } catch (Exception ex) { 
      throw Wrapper.wrap(ex); 
     } 
    }); 

    a.join(); 
} 

Và sau đó bạn có thể unwrap rằng ..

try { 
     go(); 
} catch (Wrapper w) { 
     throw w.unwrap(); 
} 
+0

tôi cần phải ném một 'ServerException' chỉ. – ayushgp

+1

@ayushgp tôi không thấy điều này xảy ra với các luồng mặc định, vì chúng không cho phép các ngoại lệ đã kiểm tra ... có thể bạn sẽ ổn với việc đóng gói một và hơn là mở khóa không? – Eugene

+2

Dường như phương thức 'go()' của bạn sẽ không bao giờ ném bất cứ thứ gì. Tôi đoán nó thiếu một cuộc gọi 'join()'. Ngoài ra, 'Wrapper' không cung cấp nhiều hơn những gì đã có sẵn với' Throwable.getCause() '. Tôi sẽ không bao gồm một ngoại lệ vào một cái khác mà không thiết lập nguyên nhân, vì nó phá vỡ quy ước và nó sẽ không in stacktraces thích hợp. –

0

Ngay cả khi câu trả lời của người khác rất hay. nhưng tôi cung cấp cho bạn một cách khác để ném ngoại lệ đã kiểm tra vào CompletableFuture.

NẾU bạn không muốn gọi một CompletableFuture trong chủ đề khác, bạn có thể sử dụng một lớp vô danh để xử lý nó, các mã như thế này:

CompletableFuture<A> a = new CompletableFuture<A>() {{ 
    try { 
     complete(someObj.someFunc()); 
    } catch (ServerException ex) { 
     completeExceptionally(ex); 
    } 
}}; 

NẾU bạn muốn gọi một CompletableFuture trong chủ đề khác, bạn cũng có thể sử dụng một lớp vô danh để xử lý nó, nhưng phương pháp chạy bởi runAsync:

CompletableFuture<A> a = new CompletableFuture<A>() {{ 
    CompletableFuture.runAsync(() -> { 
     try { 
      complete(someObj.someFunc()); 
     } catch (ServerException ex) { 
      completeExceptionally(ex); 
     } 
    }); 
}}; 
+4

Không cần phải thực hiện điều đó trong lớp con ẩn danh. Phân lớp chỉ lãng phí tài nguyên. Xem thêm [tại đây] (https://stackoverflow.com/a/43767613/2711488) và [ở đây] (https://stackoverflow.com/a/28961083/2711488)… – Holger

+0

@Holger cảm ơn bạn, thưa bạn. Tôi chỉ viết nó lên trong đầu. và tôi sẽ xem nó sau. –

+0

@Holger thưa ông, tôi thấy hai câu trả lời của bạn là khác nhau. và tôi thích cái đầu tiên bạn sử dụng trong câu hỏi này. bởi vì nó dễ sử dụng và rất rõ ràng. –