Skip to content

Add refactoring-to-async skill#79

Closed
mrsharm wants to merge 1 commit intodotnet:mainfrom
mrsharm:musharm/refactoring-to-async-skill
Closed

Add refactoring-to-async skill#79
mrsharm wants to merge 1 commit intodotnet:mainfrom
mrsharm:musharm/refactoring-to-async-skill

Conversation

@mrsharm
Copy link
Member

@mrsharm mrsharm commented Feb 23, 2026

Summary

Adds the refactoring-to-async skill that teaches bottom-up async conversion patterns for .NET codebases.

Eval Results

Skill Validation Results — refactoring-to-async

Skill Test Baseline With Skill Δ Verdict
refactoring-to-async Refactor synchronous service to async 3.3/5 5.0/5 +1.7
refactoring-to-async Async refactoring should not apply to CPU-bound code 1.0/5 1.0/5 0.0

Overall improvement: +18.8% (3 runs, not statistically significant)

Model: claude-opus-4.6 | Judge: claude-opus-4.6

What the Skill Teaches

  • Bottom-up async conversion order - start from leaf methods, propagate upward
  • CancellationToken propagation through the entire call chain
  • ConfigureAwait(false) usage in library code vs ASP.NET Core apps
  • ValueTask selection criteria
  • Identifying sync-over-async anti-patterns

Why This Skill Passes

The base model frequently struggles with the correct order of async conversion - suggesting changes to calling methods first instead of leaf methods. This skill teaches the precise procedural sequence.

Files

  • src/dotnet/skills/refactoring-to-async/SKILL.md
  • src/dotnet/tests/refactoring-to-async/eval.yaml + fixture files

@ViktorHofer
Copy link
Member

Please share the results as a comment similar to #75 (comment)
Also make sure that you test at least 3 runs (--runs 3 for local validation): https://github.com/dotnet/skills/pull/82/changes

Teaches bottom-up async conversion patterns: identifying sync-over-async,
CancellationToken propagation, ConfigureAwait usage, and ValueTask selection.

Eval results: +46.9% improvement over baseline (threshold: 10%)
Includes eval.yaml with positive scenario + negative test and fixture files.
@mrsharm mrsharm force-pushed the musharm/refactoring-to-async-skill branch from d251570 to 8c2fb53 Compare February 23, 2026 14:52
@mrsharm mrsharm changed the title Add refactoring-to-async skill with eval Add refactoring-to-async skill (+46.9% eval improvement) Feb 23, 2026
@mrsharm
Copy link
Member Author

mrsharm commented Feb 25, 2026

Skill Validation Results — refactoring-to-async

Skill Test Baseline With Skill Δ Verdict
refactoring-to-async Refactor synchronous service to async 3.3/5 5.0/5 +1.7
refactoring-to-async Async refactoring should not apply to CPU-bound code 1.0/5 1.0/5 0.0

Overall improvement: +18.8% (3 runs, not statistically significant)

Model: claude-opus-4.6 | Judge: claude-opus-4.6

1 similar comment
@mrsharm
Copy link
Member Author

mrsharm commented Feb 25, 2026

Skill Validation Results — refactoring-to-async

Skill Test Baseline With Skill Δ Verdict
refactoring-to-async Refactor synchronous service to async 3.3/5 5.0/5 +1.7
refactoring-to-async Async refactoring should not apply to CPU-bound code 1.0/5 1.0/5 0.0

Overall improvement: +18.8% (3 runs, not statistically significant)

Model: claude-opus-4.6 | Judge: claude-opus-4.6

@mrsharm mrsharm changed the title Add refactoring-to-async skill (+46.9% eval improvement) Add refactoring-to-async skill Feb 25, 2026
@mrsharm mrsharm marked this pull request as ready for review February 25, 2026 13:55
Copilot AI review requested due to automatic review settings February 25, 2026 13:55
@mrsharm
Copy link
Member Author

mrsharm commented Feb 25, 2026

Please share the results as a comment similar to #75 (comment) Also make sure that you test at least 3 runs (--runs 3 for local validation): https://github.com/dotnet/skills/pull/82/changes

Done.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new .NET skill (refactoring-to-async) along with evaluation scenarios and fixture code to teach and validate bottom-up async/await refactoring patterns (Task propagation, CancellationToken flow, and avoiding sync-over-async).

Changes:

  • Added refactoring-to-async skill documentation (SKILL.md) describing a structured async conversion workflow.
  • Added eval scenarios to validate async refactoring behavior and avoidance of async for CPU-bound work.
  • Added a C# fixture project (SyncService) used by the eval scenario as refactoring input.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/dotnet/tests/refactoring-to-async/eval.yaml New eval scenarios + assertions/rubric for async refactoring and CPU-bound guidance.
src/dotnet/tests/refactoring-to-async/UserService.cs Fixture code containing sync I/O + sync-over-async patterns for the agent to refactor.
src/dotnet/tests/refactoring-to-async/SyncService.csproj Fixture project configuration and dependency needed to compile the sample.
src/dotnet/skills/refactoring-to-async/SKILL.md New skill guidance and workflow (search patterns, conversion order, cancellation, validation).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- type: "output_matches"
pattern: "(CancellationToken|cancellation)"
- type: "output_not_matches"
pattern: "\\.Result\\b|\\.Wait\\(\\)"
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scenario requires identifying sync-over-async patterns like .Result/.Wait() (rubric line 14), but the output_not_matches assertion fails the run if the agent output contains .Result or .Wait() anywhere (including when calling them out as anti-patterns). This creates false negatives and makes the rubric effectively incompatible with the assertions. Consider tightening the regex to only match actual code usage (e.g., requiring a trailing semicolon) or removing this assertion and relying on other checks.

Suggested change
pattern: "\\.Result\\b|\\.Wait\\(\\)"
pattern: "\\.Result\\b\\s*;|\\.Wait\\(\\)\\s*;"

Copilot uses AI. Check for mistakes.
Search for synchronous I/O patterns in the codebase:

```bash
grep -rn "\.Result\b\|\.Wait()\|\.GetAwaiter()\.GetResult()\|ReadToEnd()\|\.Read()\|\.Write(" --include="*.cs" .
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The grep pattern uses \b for a word-boundary, but grep's default regex syntax does not treat \b as a word boundary (it’s typically interpreted as a backspace). Also, \.Read() only matches parameterless Read() calls and will miss the common Read(...) overloads. Consider switching to rg (ripgrep) or grep -P/grep -E with a corrected pattern (e.g., matching \.Read\( and \.Write\(, and using a portable word boundary like \<Result\> or PCRE \b with -P).

Suggested change
grep -rn "\.Result\b\|\.Wait()\|\.GetAwaiter()\.GetResult()\|ReadToEnd()\|\.Read()\|\.Write(" --include="*.cs" .
grep -rnP "\.Result\b|\.Wait\(\)|\.GetAwaiter\(\)\.GetResult\(\)|ReadToEnd\(\)|\.Read\(|\.Write\(" --include="*.cs" .

Copilot uses AI. Check for mistakes.
Search for remaining issues:

```bash
grep -rn "\.Result\b\|\.Wait()\|\.GetAwaiter()\.GetResult()" --include="*.cs" .
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as Step 1: grep without -P won't treat \b as a word boundary, so this command may not reliably find .Result usages. Adjust the command to use a regex flavor that supports word boundaries (or use \</\>), so the "should return zero results" guidance is accurate.

Suggested change
grep -rn "\.Result\b\|\.Wait()\|\.GetAwaiter()\.GetResult()" --include="*.cs" .
grep -rnP '\.Result\b|\.Wait\(\)|\.GetAwaiter\(\)\.GetResult\(\)' --include="*.cs" .

Copilot uses AI. Check for mistakes.

# Refactoring to Async

## When to Use
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move any part of use/not use into decription similar to
https://github.com/dotnet/runtime/pull/125005/changes#diff-ded9450821c1df27638def6250c00784c7f795e3a9c56ad13d09a34853a0d09bR3-R4
if it can avoid unnecessaryt reloading.

Possibly not all can go there. For example: user wants to parallelize work is something that can be known before loading the skill, and avoid load. Whereas possibly "code is CPU bound" might need loading the skill? but in general I think ?all of this should move up there.

@danmoseley
Copy link
Member

danmoseley commented Mar 2, 2026

add codeowners entry, move files around to match new pattern in main.

| `task.Result` or `task.Wait()` | Blocks thread, risks deadlock | `await task` |
| `async void` methods | Exceptions crash the process | `async Task` (except event handlers) |
| `Task.Run` wrapping async I/O | Wastes a thread pool thread | Call async method directly |
| Missing `ConfigureAwait(false)` in libraries | Can deadlock in UI/ASP.NET sync contexts | Add `ConfigureAwait(false)` in library code |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is missing from all the examples. maybe indicate those are app code?

It might be worth mentioning as instructions, not just in anti-patterns -- if code is library add .ConfigureAwait(false) to every await. It should be used consistently or not at all.

| Deadlock after conversion | Ensure `await` is used everywhere; no `.Result` mixed with `await` |
| Performance worse after conversion | Async adds overhead for CPU-bound work; only use for I/O |
| Forgetting to update tests | Test methods must return `Task` and use `await` |
| Breaking interface consumers | Consider keeping sync wrappers temporarily during staged migration |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may be a big high level/conceptual for this skill?

| `Thread.Sleep(ms)` | `await Task.Delay(ms, ct)` |
| `task.Result` | `await task` |
| `task.Wait()` | `await task` |

Copy link
Member

@danmoseley danmoseley Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| `reader.Read()` (DbDataReader) | `await reader.ReadAsync()` |
| `command.ExecuteNonQuery()` (DbCommand) | `await command.ExecuteNonQueryAsync()` |

maybe

timeout: 120

- name: "Async refactoring should not apply to CPU-bound code"
prompt: "I have a method that does heavy matrix multiplication using nested for loops. Can you make it faster?"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this really something the user would write? seems like a gimme as written. more likely the user would say "make DoIt() async so it's faster" and the AI would have to figure out that it's CPU bound?

- "Did NOT suggest converting CPU-bound computation to async/await"
- "Suggested parallelism approaches (Parallel.For, Task.Run, SIMD, or algorithmic optimization)"
- "Correctly identified that async is for I/O-bound work, not CPU-bound work"
timeout: 60
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you expect ConfigureAwait(false) in this UserService case? either way, should be a test for the opposite case (eg winforms) and verify in each case it's present or not as expected

Comment on lines +143 to +148
| Error | Fix |
|---|---|
| `CS4032`: `await` in non-async method | Add `async` to the method signature and return `Task` or `Task<T>` |
| `CS0029`: Cannot convert `Task<T>` to `T` | Add `await` before the call |
| `CS0127`: Method returns `Task` but body returns value | Change return type to `Task<T>` |
| `CS1998`: Async method lacks `await` | Remove `async` if no awaits are needed, or the method is genuinely sync |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| Error | Fix |
|---|---|
| `CS4032`: `await` in non-async method | Add `async` to the method signature and return `Task` or `Task<T>` |
| `CS0029`: Cannot convert `Task<T>` to `T` | Add `await` before the call |
| `CS0127`: Method returns `Task` but body returns value | Change return type to `Task<T>` |
| `CS1998`: Async method lacks `await` | Remove `async` if no awaits are needed, or the method is genuinely sync |
| Error | Fix |
|---|---|
Error | Fix |
|---|---|
| `CS4032`: `await` in non-async method | Add `async` to the method signature and return `Task` or `Task<T>` |
| `CS0029`: Cannot convert `Task<T>` to `T` | Add `await` before the call |
| `CS0127`: Method returns `Task` but body returns value | Change return type to `Task<T>` |
| `CS1983`: Return type of async method must be void, Task, Task<T>, ValueTask, or ValueTask<T> | Wrap the return type: `async string Foo()``async Task<string> Foo()` |
| `CS1998`: Async method lacks `await` | Remove `async` if no awaits are needed, or the method is genuinely sync |
| `CS0535`: Does not implement interface member | Update the interface to match the new async signatures (or vice versa) |
| `CS7036`: No argument given for required parameter `cancellationToken` | Add `ct` argument at call sites after adding `CancellationToken` to signatures |
| `CS1503`: Argument type mismatch (`Task<T>` passed where `T` expected) | Add `await` at the call site |

if you want to add more?

Copy link
Member

@danmoseley danmoseley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mrsharm
Copy link
Member Author

mrsharm commented Mar 6, 2026

Closing: replaced by new PR from mrsharm/skills with plugins/ directory structure.

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.

4 participants