

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 錯誤處理
<a name="errorhandling"></a>

**Topics**
+ [TryCatchFinally 語意](#errorhandling.trycatchfinally)
+ [取消](#test.cancellation.resources)
+ [巢狀 TryCatchFinally](#errorhandling.nested)

Java 中的 `try`/`catch`/`finally` 建構可簡化錯誤處理，而且隨時可以使用。此建構可讓您建立錯誤處理器與程式碼區塊的關聯。就內部而言，其運作方式為填入呼叫堆疊上錯誤處理器的其他中繼資料。拋出例外狀況時，執行時間會查看相關聯錯誤處理器的呼叫堆疊，並呼叫之；如果找不到適當的錯誤處理器，則會將例外狀況傳播到呼叫鏈。

這適用於同步程式碼，但處理非同步和分散式程式中的錯誤會造成其他挑戰。由於非同步呼叫會立即傳回，因此當非同步程式碼執行時，呼叫者不會在呼叫堆疊上。這表示發起人無法如常處理非同步程式碼中的未處理例外狀況。一般而言，會透過將錯誤狀態傳遞給已傳遞到非同步方法的回呼，來處理源自非同步程式碼的例外狀況。或者，如果使用 `Future<?>`，則會在您嘗試存取它時報告錯誤。這麼做比較不恰當，因為收到例外狀況的程式碼 (使用 `Future<?>` 的回呼或程式碼) 沒有原始呼叫的內容，而且可能無法適當地處理例外狀況。甚至，在分散式非同步系統中，如果並行執行元件，則可能會同時發生多個錯誤。這些錯誤可以是不同的類型和嚴重性，而且需要適當地加以處理。

在非同步呼叫之後清除資源也十分困難。與同步程式碼不同，您無法在呼叫程式碼中使用 try/catch/finally 來清除資源，因為當最後區塊執行時，在嘗試區塊中啟動的工作可能仍在進行中。

框架提供一種機制，讓分散式非同步程式碼中的錯誤處理類似 Java 的 try/catch/finally，而且幾乎像 Java 的 try/catch/finally 一樣簡單。

```
ImageProcessingActivitiesClient activitiesClient
     = new ImageProcessingActivitiesClientImpl();

public void createThumbnail(final String webPageUrl) {

  new TryCatchFinally() {

    @Override
    protected void doTry() throws Throwable {
      List<String> images = getImageUrls(webPageUrl);
      for (String image: images) {
        Promise<String> localImage
            = activitiesClient.downloadImage(image);
        Promise<String> thumbnailFile
            = activitiesClient.createThumbnail(localImage);
        activitiesClient.uploadImage(thumbnailFile);
      }
    }

    @Override
    protected void doCatch(Throwable e) throws Throwable {

      // Handle exception and rethrow failures
      LoggingActivitiesClient logClient = new LoggingActivitiesClientImpl();
      logClient.reportError(e);
      throw new RuntimeException("Failed to process images", e);
    }

    @Override
    protected void doFinally() throws Throwable {
      activitiesClient.cleanUp();
    }
  };
}
```

`TryCatchFinally` 類別與其變體 `TryFinally` 和 `TryCatch` 的運作方式與 Java 的 `try`/`catch`/`finally` 類似。使用它，即可建立例外狀況處理器與工作流程程式碼區塊的關聯，而工作流程程式碼區塊可能會以非同步與遠端任務執行。`doTry()` 方法在邏輯上等於 `try` 區塊。框架會自動執行 `doTry()` 中的程式碼。`Promise` 物件清單可以傳遞給 `TryCatchFinally` 的建構函數。所有傳入建構函數的 `Promise `物件都準備就緒時，將會執行 `doTry` 方法。如果從 `doTry()` 非同步呼叫的程式碼引發例外狀況，則會取消 `doTry()` 中的任何待定工作，並呼叫 `doCatch()` 來處理例外狀況。例如，在上面的清單中，如果 `downloadImage` 拋出例外狀況，則會取消 `createThumbnail` 和 `uploadImage`。最後，完成所有非同步工作 (已完成、失敗或已取消) 時，會呼叫 `doFinally()`。它可以用於資源清理。您也可以將這些類別巢狀處理，以符合您的需求。

在 `doCatch()` 中報告例外狀況時，框架會提供包含非同步和遠端呼叫的完整邏輯呼叫堆疊。偵錯時，這可能十分有用，特別是您有呼叫其他非同步方法的非同步方法時。例如，downloadImage 的例外狀況將會產生與下列類似的例外狀況：

```
RuntimeException: error downloading image
  at downloadImage(Main.java:35)
  at ---continuation---.(repeated:1)
  at errorHandlingAsync$1.doTry(Main.java:24)
  at ---continuation---.(repeated:1)
…
```

## TryCatchFinally 語意
<a name="errorhandling.trycatchfinally"></a>

 AWS Flow Framework 適用於 Java 的 程式的執行可以視覺化為同時執行分支的樹狀結構。非同步方法、活動和 `TryCatchFinally` 本身的呼叫會在此執行的樹狀目錄中建立新分支。例如，影像處理工作流程可以檢視為下圖中所顯示的樹狀目錄。

![\[非同步執行樹狀目錄\]](http://docs.aws.amazon.com/zh_tw/amazonswf/latest/awsflowguide/images/trycatchfinally.png)


某個執行分支中的錯誤將會導致回溯該分支，就像例外狀況導致回溯 Java 程式中的呼叫堆疊一樣。回溯會繼續移至執行分支，直到錯誤已處理或到達樹狀目錄根，在此情況下，會終止工作流程執行。

框架會報告將任務處理為例外狀況時所發生的錯誤。它會建立下列兩者的關聯：`TryCatchFinally` 中所定義的例外狀況處理器 (`doCatch()` 方法) 與對應 `doTry()` 中程式碼所建立的所有任務。如果任務失敗，例如由於逾時或未處理的例外狀況，則會引發適當的例外狀況，並`doCatch()`叫用對應的 來處理。為了達成此目的，框架與 Amazon SWF 一起運作，以傳播遠端錯誤，並在發起人的內容中將其作為例外狀況重新排除。

## 取消
<a name="test.cancellation.resources"></a>

同步程式碼中發生例外狀況時，控制會直接跳至 `catch` 區塊，並跳過 `try` 區塊中剩餘的程式碼。例如：

```
try {
    a();
    b();
    c();
}
catch (Exception e) {
    e.printStackTrace();
}
```

在此程式碼中，如果 `b()` 拋出例外狀況，則絕不會呼叫 `c()`。與工作流程比較：

```
new TryCatch() {

    @Override
    protected void doTry() throws Throwable {
        activityA();
        activityB();
        activityC();
    }

    @Override
    protected void doCatch(Throwable e) throws Throwable {
        e.printStackTrace();
    }
};
```

在此情況下，`activityA`、`activityB` 和 `activityC` 呼叫都會順利傳回，並導致建立三個非同步執行的任務。假設稍後 `activityB` 的任務導致錯誤。此錯誤由 Amazon SWF 記錄在歷史記錄中。若要處理此情況，框架會先嘗試取消源自相同 `doTry()` 範圍的所有其他任務；在此情況下為 `activityA` 和 `activityC`。所有這類任務完成時 (取消、失敗或順利完成)，都會呼叫適當的 `doCatch()` 方法來處理錯誤。

與永不執行 `c()` 的同步範例不同，會呼叫 `activityC`，並排定執行任務；因此，框架會嘗試予以取消，但不保證會確實取消。由於活動可能已經完成、可能忽略取消請求，或可能因錯誤而失敗，因而無法保證取消。不過，框架能夠保證只有在完成從對應 `doTry()` 啟動的所有任務之後，才會呼叫 `doCatch()`。也能保證只有在完成從 `doTry()` 和 `doCatch()` 啟動的所有任務之後，才會呼叫 `doFinally()`。例如，如果上述範例中的活動彼此相依，假設 `activityB` 相依於 `activityA`和 `activityC` `activityB`，則 的取消`activityC`會立即生效，因為在 `activityB` 完成之前不會在 Amazon SWF 中排程：

```
new TryCatch() {

    @Override
    protected void doTry() throws Throwable {
        Promise<Void> a = activityA();
        Promise<Void> b = activityB(a);
        activityC(b);
    }

    @Override
    protected void doCatch(Throwable e) throws Throwable {
        e.printStackTrace();
    }
};
```

### 活動訊號
<a name="errorhandling.activity.heartbeat"></a>

 AWS Flow Framework 適用於 Java 的合作取消機制允許正常取消傳輸中的活動任務。觸發取消時，會自動取消封鎖或等待指派給工作者的任務。不過，如果任務已指派給工作者，則框架會請求取消活動。活動實作必須明確地處理這類取消請求。作法是報告您活動的活動訊號。

報告活動訊號允許活動實作報告進行中之活動任務 (對監控很有幫助) 的進度，且可讓活動檢查取消請求。如果已請求取消，則 `recordActivityHeartbeat` 方法將拋出 `CancellationException`。活動實作可以截獲此例外狀況，並處理取消請求，或者可以抑制例外狀況來忽略請求。若要使用取消請求，活動應該執行所需的清除 (如果有的話)，然後重新拋出 `CancellationException`。從活動實作拋出此例外狀況時，框架會記錄已完成活動任務，但狀態為已取消。

下列範例顯示可下載和處理影像的活動。活動會在處理每個影像之後發出活動訊號，而且，如果請求取消，則會清除和重新拋出例外狀況來確認取消。

```
@Override
public void processImages(List<String> urls) {
    int imageCounter = 0;
    for (String url: urls) {
        imageCounter++;
        Image image = download(url);
        process(image);
        try {
            ActivityExecutionContext context
                 = contextProvider.getActivityExecutionContext();
            context.recordActivityHeartbeat(Integer.toString(imageCounter));
        } catch(CancellationException ex) {
            cleanDownloadFolder();
            throw ex;
        }
    }
}
```

不需要報告活動訊號，但若您的活動是長時間執行，或可能會執行要在錯誤情況下取消的高成本操作時，則建議報告活動訊號。您應該從活動實作定期呼叫 `heartbeatActivityTask`。

如果活動逾時，則會拋出 `ActivityTaskTimedOutException`，而且例外狀況物件上的 `getDetails` 會傳回資料，而這項資料會傳遞給對應活動任務的最後一個成功 `heartbeatActivityTask` 呼叫。工作流程實作可能會使用這項資訊來判斷活動任務逾時之前的進度。

**注意**  
由於 Amazon SWF 可能會調節活動訊號請求，因此活動訊號頻率太頻繁並非最佳實務。如需 [Amazon SWF 設定的限制，請參閱 Amazon Simple Workflow Service 開發人員指南](https://docs.aws.amazon.com/amazonswf/latest/developerguide/)。 Amazon SWF

### 明確地取消任務
<a name="errorhandling.canceltask"></a>

除了錯誤情況外，您也可能在其他情況下明確取消任務。例如，如果使用者取消訂單，則可能需要取消使用信用卡處理付款的活動。框架可讓您明確地取消 `TryCatchFinally` 範圍中所建立的任務。在下列範例中，如果在處理付款時收到訊號，則會取消付款任務。

```
public class OrderProcessorImpl implements OrderProcessor {
    private PaymentProcessorClientFactory factory
        = new PaymentProcessorClientFactoryImpl();
    boolean processingPayment = false;
    private TryCatchFinally paymentTask = null;

    @Override
    public void processOrder(int orderId, final float amount) {
        paymentTask = new TryCatchFinally() {

            @Override
            protected void doTry() throws Throwable {
                processingPayment = true;

                PaymentProcessorClient paymentClient = factory.getClient();
                paymentClient.processPayment(amount);
            }

            @Override
            protected void doCatch(Throwable e) throws Throwable {
                if (e instanceof CancellationException) {
                    paymentClient.log("Payment canceled.");
                } else {
                    throw e;
                }
            }

            @Override
            protected void doFinally() throws Throwable {
                processingPayment = false;
            }
        };

    }

    @Override
    public void cancelPayment() {
        if (processingPayment) {
            paymentTask.cancel(null);
        }
    }
}
```

### 接收已取消之任務的通知
<a name="errorhandling.canceltask.notification"></a>

任務完成且處於已取消狀態時，框架會拋出 `CancellationException` 來通知工作流程邏輯。任務完成且處於已取消狀態時，會在歷史記錄中建立記錄，而且框架會以 `CancellationException` 呼叫適當的 `doCatch()`。如上述範例所示，在取消付款處理任務之時，工作流程會收到 `CancellationException`。

未處理的 `CancellationException` 會傳播到執行分支，如同其他例外狀況。不過，只有在範圍中沒有其他例外狀況時，`doCatch()` 方法才會收到 `CancellationException`；其他例外狀況的優先順序高於取消。

## 巢狀 TryCatchFinally
<a name="errorhandling.nested"></a>

您可以將 `TryCatchFinally` 巢狀處理，以符合您的需求。由於每個 `TryCatchFinally` 都會在執行樹狀目錄中建立新的分支，因此您可以建立巢狀範圍。父範圍中的例外狀況將會導致嘗試取消透過在其內將 `TryCatchFinally` 巢狀處理而啟動的所有任務。不過，巢狀 `TryCatchFinally` 中的例外狀況不會自動傳播至父項。如果您想要將例外狀況從巢狀 `TryCatchFinally` 傳播至其內含的 `TryCatchFinally`，則應該在 `doCatch()` 中重新拋出例外狀況。換言之，只會發出未處理的例外狀況，就像 Java 的 `try`/`catch` 一樣。如果您呼叫取消方法來取消巢狀 `TryCatchFinally`，則會取消巢狀 `TryCatchFinally`，但不會自動取消內含的 `TryCatchFinally`。

![\[巢狀 TryCatchFinally\]](http://docs.aws.amazon.com/zh_tw/amazonswf/latest/awsflowguide/images/nested.png)


```
new TryCatch() {
    @Override
    protected void doTry() throws Throwable {
        activityA();

        new TryCatch() {
            @Override
            protected void doTry() throws Throwable {
                activityB();
            }

            @Override
            protected void doCatch(Throwable e) throws Throwable {
                reportError(e);
            }
        };

        activityC();
    }

    @Override
    protected void doCatch(Throwable e) throws Throwable {
        reportError(e);
    }
};
```