The IModel.DownloadAsync(Action<float>, CancellationToken) method accepts a CancellationToken parameter but does not properly check or honor cancellation requests during the download operation. This results in downloads continuing to completion even after cancellation has been explicitly requested, making it impossible to reliably cancel long-running model downloads.
Environment
- SDK: Microsoft.AI.Foundry.Local.WinML v0.8.2.1
- Platform: Windows
- Application: WinUI 3 Desktop App
- .NET Version: .NET 8.0
Problem
When calling model.DownloadAsync(progressCallback, cancellationToken) and subsequently canceling the operation via CancellationTokenSource.Cancel(), the download operation continues to execute until completion (100%) despite the cancellation request. The SDK only appears to check the cancellation token during the subsequent LoadAsync() operation, not during the actual download phase.
This behavior violates the standard .NET async cancellation pattern where operations accepting a CancellationToken should periodically check token.IsCancellationRequested and throw OperationCanceledException when cancellation is requested.
Repro Step
- Initialize a FoundryLocal catalog and obtain a model reference:
var catalog = await foundryManager.GetCatalogAsync();
var model = await catalog.GetModelAsync(alias);
- Start a model download with cancellation support:
var cts = new CancellationTokenSource();
var downloadTask = model.DownloadAsync(
progressPercent => {
Debug.WriteLine($"Download progress: {progressPercent}%");
},
cts.Token
);
- During download (e.g., at 50-60% progress), request cancellation:
- Observe that the download continues to 100% completion despite cancellation request.
Actual Behavior
Debug Output Evidence:
[FoundryClient] Download progress: 42.44%
[FoundryClient] Download progress: 49.79%
[App] User clicked cancel button
[App] Calling CancellationTokenSource.Cancel()
[App] CancellationToken AFTER Cancel: IsCancellationRequested: True
[FoundryClient] Progress callback - CancellationToken is REQUESTED!
[FoundryClient] Download progress: 57.19%
[FoundryClient] Download progress: 65.31%
[FoundryClient] Download progress: 73.44%
[FoundryClient] Download progress: 81.56%
[FoundryClient] Download progress: 89.69%
[FoundryClient] Download progress: 97.81%
[FoundryClient] Download progress: 100.00%
[FoundryClient] ========== SDK DownloadAsync COMPLETED ==========
[FoundryClient] Starting model preparation...
[FoundryClient] OperationCanceledException in PrepareModelAsync: The operation was canceled.
Key Observations:
CancellationToken.IsCancellationRequested returns True immediately after Cancel() is called
- The progress callback continues to be invoked with increasing percentages (57% → 100%)
- Within the progress callback, we can verify
token.IsCancellationRequested == True, but the SDK ignores it
DownloadAsync completes successfully without throwing OperationCanceledException
- Only the subsequent
LoadAsync() operation respects the cancellation token
Expected Behavior
When CancellationToken.Cancel() is called:
- The SDK should detect
cancellationToken.IsCancellationRequested == True during the download loop
DownloadAsync should stop the download operation promptly
DownloadAsync should throw OperationCanceledException to signal cancellation
- Partial downloads should be cleaned up appropriately
- The operation should not continue to 100% completion
This aligns with the standard .NET async cancellation pattern documented in Microsoft's async programming guidelines.
Impact
- Users cannot cancel large model downloads, leading to wasted bandwidth and storage
- UI "Cancel" buttons appear non-functional, creating confusion
- No way to interrupt mistaken downloads of large models (multi-GB files)
- Applications cannot implement reliable cancellation for download operations
- Poor user experience in applications using Foundry Local
Related Code References
IModel.DownloadAsync(Action<float>, CancellationToken) - Primary method affected
IModel.LoadAsync(CancellationToken) - Correctly honors cancellation
Additional Context
This issue was discovered during user testing when users attempted to cancel large model downloads. The debug logging was added specifically to trace the cancellation flow, confirming that the application code correctly propagates the cancellation token through all layers, but the SDK's DownloadAsync method does not check it.
The fact that LoadAsync() properly throws OperationCanceledException when the token is canceled demonstrates that the SDK infrastructure supports cancellation - it just needs to be implemented consistently across all async operations, particularly DownloadAsync.
Thank you for considering this issue. Proper cancellation support is critical for production applications managing large model downloads. Please let me know if you need any additional information or testing.
The
IModel.DownloadAsync(Action<float>, CancellationToken)method accepts aCancellationTokenparameter but does not properly check or honor cancellation requests during the download operation. This results in downloads continuing to completion even after cancellation has been explicitly requested, making it impossible to reliably cancel long-running model downloads.Environment
Problem
When calling
model.DownloadAsync(progressCallback, cancellationToken)and subsequently canceling the operation viaCancellationTokenSource.Cancel(), the download operation continues to execute until completion (100%) despite the cancellation request. The SDK only appears to check the cancellation token during the subsequentLoadAsync()operation, not during the actual download phase.This behavior violates the standard .NET async cancellation pattern where operations accepting a
CancellationTokenshould periodically checktoken.IsCancellationRequestedand throwOperationCanceledExceptionwhen cancellation is requested.Repro Step
Actual Behavior
Debug Output Evidence:
Key Observations:
CancellationToken.IsCancellationRequestedreturnsTrueimmediately afterCancel()is calledtoken.IsCancellationRequested == True, but the SDK ignores itDownloadAsynccompletes successfully without throwingOperationCanceledExceptionLoadAsync()operation respects the cancellation tokenExpected Behavior
When
CancellationToken.Cancel()is called:cancellationToken.IsCancellationRequested == Trueduring the download loopDownloadAsyncshould stop the download operation promptlyDownloadAsyncshould throwOperationCanceledExceptionto signal cancellationThis aligns with the standard .NET async cancellation pattern documented in Microsoft's async programming guidelines.
Impact
Related Code References
IModel.DownloadAsync(Action<float>, CancellationToken)- Primary method affectedIModel.LoadAsync(CancellationToken)- Correctly honors cancellationAdditional Context
This issue was discovered during user testing when users attempted to cancel large model downloads. The debug logging was added specifically to trace the cancellation flow, confirming that the application code correctly propagates the cancellation token through all layers, but the SDK's
DownloadAsyncmethod does not check it.The fact that
LoadAsync()properly throwsOperationCanceledExceptionwhen the token is canceled demonstrates that the SDK infrastructure supports cancellation - it just needs to be implemented consistently across all async operations, particularlyDownloadAsync.Thank you for considering this issue. Proper cancellation support is critical for production applications managing large model downloads. Please let me know if you need any additional information or testing.