Skip to content

Add async/await support for all long-running DISM operations (fixes #82)#285

Merged
jeffkl merged 10 commits intojeffkl:mainfrom
Rolling2405:copilot/add-async-await-support-dism-operations
Apr 15, 2026
Merged

Add async/await support for all long-running DISM operations (fixes #82)#285
jeffkl merged 10 commits intojeffkl:mainfrom
Rolling2405:copilot/add-async-await-support-dism-operations

Conversation

@Rolling2405
Copy link
Copy Markdown
Contributor

@Rolling2405 Rolling2405 commented Mar 30, 2026

Closes #82

The DISM API is entirely synchronous, blocking the calling thread during long-running native operations. This causes UI freezes in WPF/WinForms/MAUI apps. As noted in #82, wrapping calls in Task.Run alone does not solve the problem because the native progress callback fires on the calling thread.

This PR adds *Async counterparts for all 17 public methods that accept a DismProgressCallback, using the TaskCompletionSource pattern suggested by @jeffkl in the issue thread.

Implementation

  • Pattern: TaskCompletionSource<bool> + Task.Factory.StartNew with TaskCreationOptions.LongRunning — not Task.Run, since native DISM calls are truly long-running blocking operations
  • Progress: IProgress<DismProgress> instead of DismProgressCallback (standard .NET async pattern)
  • Cancellation: CancellationToken wired to DismProgress.Cancel = true, which signals the native EventWaitHandle. CancellationToken.None is passed to Task.Factory.StartNew to ensure the delegate always runs — cancellation is handled inside the delegate to avoid hanging tcs.Task

Usage

// Before: blocks the calling thread
DismApi.AddPackage(session, packagePath, ignoreCheck: true,
    preventPending: false, callback, null);

// After: non-blocking with standard IProgress<T> / CancellationToken
await DismApi.AddPackageAsync(session, packagePath, ignoreCheck: true,
    preventPending: false,
    progress: new Progress<DismProgress>(p => progressBar.Value = p.Current),
    cancellationToken: cts.Token);

@Rolling2405
Copy link
Copy Markdown
Contributor Author

Hi @jeffkl, I’m aware there’s a merge conflict with main. I’m away from my computer for a while — since maintainer edits are enabled, feel free to resolve it directly if you’d like to review sooner. Happy to fix it myself when I’m back if you’d prefer. Thanks for considering this!

@jeffkl
Copy link
Copy Markdown
Owner

jeffkl commented Mar 30, 2026

I'm going to push a bunch of changes, I want to enforce a coding style but realized I don't have enough in place to do so. Commits incoming...

@jeffkl jeffkl force-pushed the copilot/add-async-await-support-dism-operations branch from 46ac78e to 41eafc9 Compare March 30, 2026 17:46
@jeffkl
Copy link
Copy Markdown
Owner

jeffkl commented Mar 30, 2026

@Rolling2405 there are other operations that don't use progress but can take some time like opening a session, adding/removing drivers, etc. Should we add Async methods for everything? Those one's just wouldn't have progress. I never use the API in a UI so I'm not sure what users would want.

@Rolling2405
Copy link
Copy Markdown
Contributor Author

Rolling2405 commented Mar 30, 2026

Yes, I think it makes sense to add async versions for those too even without progress. Any operation that can block the UI thread is worth making awaitable, so callers aren’t forced to wrap everything in Task.Run themselves.

@Rolling2405
Copy link
Copy Markdown
Contributor Author

@jeffkl what do you think about this?

@jeffkl jeffkl self-assigned this Mar 31, 2026
@jeffkl
Copy link
Copy Markdown
Owner

jeffkl commented Mar 31, 2026

@Rolling2405 sorry been slammed as of late and I'm out on vacation starting tomorrow. I've noticed that some of the new tests fail sporadically when I run all tests together but not when I run them individually, been trying to debug that.

I'm completely fine with making all APIs that take some time but don't have progress be async.

Rolling2405 added a commit to Rolling2405/ManagedDism that referenced this pull request Apr 1, 2026
Addresses the sporadic test failures jeffkl identified while debugging
PR jeffkl#285, where tests passed individually but failed when run together.

CommitImageAsync tests were passing DISM_DISCARD_IMAGE flag (1) to the
native DismCommitImage function, which fails with a DismException on the
CI test WIM image. Changed to DISM_COMMIT_IMAGE flag (0), which is a
safe no-op when no changes have been made.

Fixes the remaining 2 of 114 test failures in the .NET 10 CI step:
- CommitImageAsyncWithProgress
- CommitImageAsyncHappyPath
@Rolling2405
Copy link
Copy Markdown
Contributor Author

@jeffkl When you get a chance, can you please take a look and see if things look good for merging? Thanks!

Rolling2405 and others added 9 commits April 14, 2026 09:09
…braryImport

Added net10.0 to TargetFrameworks with AllowUnsafeBlocks for source-generated P/Invoke - Converted all 48 DllImport declarations to LibraryImport with #if NET7_0_OR_GREATER conditional compilation - Added [UnmanagedFunctionPointer] to native callback delegate - Moved ArtifactsPath to Directory.Build.props (NETSDK1199 fix for .NET 10) - All 4 targets build successfully: net40, netstandard2.0, net8.0, net10.0
- SA1116: Collapse multi-line DismProgress constructor to single line
- SA1116: Move first param of Task.Factory.StartNew to its own line
- SA1117: Split closing params of Task.Factory.StartNew onto separate lines

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Rolling2405 <89894749+Rolling2405@users.noreply.github.com>
… blocks

Reindent lambda body and trailing parameters in all 12 async method
files so that the opening brace, body, and remaining arguments are
consistently indented at 16 spaces (one level deeper than the method
call), satisfying SA1137 (same indentation) and SA1117 (each parameter
on its own line).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Rolling2405 <89894749+Rolling2405@users.noreply.github.com>
the private sync helper methods in 4 files to satisfy SA1202
('public' members should come before 'private' members):
- DismApi.EnableFeature.cs
- DismApi.MountImage.cs
- DismApi.RemovePackage.cs
- DismApi.SetEdition.cs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Rolling2405 <89894749+Rolling2405@users.noreply.github.com>
…RS0016/RS0017 for net40

- Remove optional parameter defaults from MountImageAsync overloads to fix RS0026
- Add all 17 public async method signatures to PublicAPI.Unshipped.txt

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Rolling2405 <89894749+Rolling2405@users.noreply.github.com>
@jeffkl
Copy link
Copy Markdown
Owner

jeffkl commented Apr 14, 2026

I'm back from vacation, I'm taking a look. Copilot didn't do a very good job on the implementation but I'm making progress...

@jeffkl
Copy link
Copy Markdown
Owner

jeffkl commented Apr 14, 2026

@Rolling2405 question for you. Not all DISM functions take progress, so I don't think they can be canceled. For example. DismAddDriver.

If we add DismApi.AddDriverAsync(DismSession, string, bool, CancellationToken), then cancellation would happen on the task started by Task.Factory.StartNew() rather than DISM itself, meaning if the caller cancelled it, the task would still run to completion in the background.

So should we add async calls to all possible methods after all? Or just to the ones that support progress and therefore can actually be canceled by DISM?

@Rolling2405
Copy link
Copy Markdown
Contributor Author

@jeffkl In that case maybe we should just add it to the ones that support progress so the user can actually cancel when they want to

@jeffkl jeffkl force-pushed the copilot/add-async-await-support-dism-operations branch from 49e1448 to 2c2c360 Compare April 15, 2026 00:45
@jeffkl jeffkl merged commit ef5036c into jeffkl:main Apr 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suggestion: Async/Await implementation.

3 participants