**From version 8.5.0 EPPlus supports cancelling a running formula calculation via the standard .NET `CancellationToken` mechanism. This is useful when processing untrusted or complex workbooks where calculation time is unbounded, for example in server-side scenarios where a timeout or resource limit needs to be enforced.**

> **Note:** `CancellationToken` support is available for .NET 4.0 and later. It is not available when targeting .NET 3.5.

## How it works

The `CancellationToken` is set on the `ExcelCalculationOption` object and passed to any of the `Calculate` overloads that accept an options parameter or a configuration handler.

```csharp
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

package.Workbook.Calculate(opt =>
{
    opt.CancellationToken = cts.Token;
});
```

```vbnet
Using cts As New CancellationTokenSource(TimeSpan.FromSeconds(30))
    package.Workbook.Calculate(Sub(opt)
                                   opt.CancellationToken = cts.Token
                               End Sub)
End Using
```

When the token is signalled, the calculation engine will throw an `OperationCanceledException` at the next cancellation checkpoint. Checkpoints are evaluated per cell and per worksheet, so the cancellation is responsive without any measurable performance overhead on normal calculations.

## Cancelled workbooks are inconsistent

When a calculation is cancelled, the workbook is left in a **partially calculated, inconsistent state**. EPPlus marks the workbook as cancelled and prevents any further use of it:

- `Save()`, `SaveAs()` and `GetAsByteArray()` will throw `InvalidOperationException`
- Calling `Calculate()` again on the same workbook will throw `InvalidOperationException`
- The property `Workbook.IsCalculationInconsistent` returns `true`

A cancelled workbook must be **disposed**. If the workbook data is still needed, reload it from the source.

## Timeout-based cancellation

The most common use case is cancelling after a fixed time limit. Use `CancellationTokenSource` with a `TimeSpan`:

```csharp
using var package = new ExcelPackage(stream);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

try
{
    package.Workbook.Calculate(opt =>
    {
        opt.CancellationToken = cts.Token;
    });

    // Calculation completed — safe to save
    package.SaveAs(outputStream);
}
catch (OperationCanceledException)
{
    // Calculation exceeded 30 seconds — discard the workbook
    logger.Warn("Calculation cancelled due to timeout.");
}
// The using statement disposes the package automatically
```

```vbnet
Using package As New ExcelPackage(stream)
    Using cts As New CancellationTokenSource(TimeSpan.FromSeconds(30))
        Try
            package.Workbook.Calculate(Sub(opt)
                                           opt.CancellationToken = cts.Token
                                       End Sub)

            ' Calculation completed — safe to save
            package.SaveAs(outputStream)
        Catch ex As OperationCanceledException
            ' Calculation exceeded 30 seconds — discard the workbook
            logger.Warn("Calculation cancelled due to timeout.")
        End Try
    End Using
End Using
' The Using statement disposes the package automatically
```

## Cancellation from another thread

Because `Calculate()` is a synchronous, blocking call, cancellation must be triggered from a different thread. A common pattern is to run the calculation in a background thread and cancel it from the main thread:

```csharp
using var package = new ExcelPackage(stream);
using var cts = new CancellationTokenSource();
Exception calcException = null;

var calcThread = new Thread(() =>
{
    try
    {
        package.Workbook.Calculate(opt => opt.CancellationToken = cts.Token);
    }
    catch (OperationCanceledException ex)
    {
        calcException = ex;
    }
});

calcThread.Start();

// Cancel after 10 seconds, or based on an external signal
Thread.Sleep(TimeSpan.FromSeconds(10));
cts.Cancel();

calcThread.Join();

if (calcException != null || package.Workbook.IsCalculationInconsistent)
{
    // Workbook is unusable — discard it
}
```

```vbnet
Using package As New ExcelPackage(stream)
    Using cts As New CancellationTokenSource()
        Dim calcException As Exception = Nothing

        Dim calcThread As New Thread(Sub()
                                         Try
                                             package.Workbook.Calculate(Sub(opt) opt.CancellationToken = cts.Token)
                                         Catch ex As OperationCanceledException
                                             calcException = ex
                                         End Try
                                     End Sub)

        calcThread.Start()

        ' Cancel after 10 seconds, or based on an external signal
        Thread.Sleep(TimeSpan.FromSeconds(10))
        cts.Cancel()

        calcThread.Join()

        If calcException IsNot Nothing OrElse package.Workbook.IsCalculationInconsistent Then
            ' Workbook is unusable — discard it
        End If
    End Using
End Using
```

## Checking the workbook state

The `IsCalculationInconsistent` property lets you check whether a workbook has been left in a cancelled state, which is useful in more advanced workflows where the exception may have been caught further up the call stack:

```csharp
if (package.Workbook.IsCalculationInconsistent)
{
    // Do not use this workbook further
    package.Dispose();
}
```

```vbnet
If package.Workbook.IsCalculationInconsistent Then
    ' Do not use this workbook further
    package.Dispose()
End If
```

## Worksheet and range calculation

Cancellation works the same way when calculating a single worksheet or a range:

```csharp
// Worksheet
worksheet.Calculate(opt => opt.CancellationToken = cts.Token);

// Range
worksheet.Cells["A1:D1000"].Calculate(opt => opt.CancellationToken = cts.Token);
```

```vbnet
' Worksheet
worksheet.Calculate(Sub(opt) opt.CancellationToken = cts.Token)

' Range
worksheet.Cells("A1:D1000").Calculate(Sub(opt) opt.CancellationToken = cts.Token)
```

In both cases, if the token is signalled, `OperationCanceledException` is thrown and the parent workbook is marked as inconsistent.

## See also

- [Formula Calculation](<https://epplussoftware.com/docs/formulas-and-filters/formula-calculation/index.md>)
