MissingAwaitDetector.Analyzers 1.0.1

dotnet add package MissingAwaitDetector.Analyzers --version 1.0.1
                    
NuGet\Install-Package MissingAwaitDetector.Analyzers -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MissingAwaitDetector.Analyzers" Version="1.0.1">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MissingAwaitDetector.Analyzers" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="MissingAwaitDetector.Analyzers">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MissingAwaitDetector.Analyzers --version 1.0.1
                    
#r "nuget: MissingAwaitDetector.Analyzers, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MissingAwaitDetector.Analyzers@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MissingAwaitDetector.Analyzers&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=MissingAwaitDetector.Analyzers&version=1.0.1
                    
Install as a Cake Tool

MissingAwaitDetector

NuGet NuGet Downloads

Your code compiles. Your code runs. Your code is wrong.

Ever spent hours debugging why your API returns System.Threading.Tasks.Task1[UserDto] instead of actual user data? Or worse — you passed a Task<int> where an int was expected, and now you're staring at a Task ID wondering why your "order total" is 42917381?

These bugs don't throw. They don't crash. They just silently do the wrong thing. And they cost real time — time you could've spent building something instead of questioning your sanity.

Why This Exists

In an ideal world, you'd never need this package. Your codebase would have perfect async discipline from day one. But we don't live in that world — we live in the world of tight deadlines, legacy codebases, and that one PR where someone forgot an await and it somehow passed code review.

MissingAwaitDetector is a Roslyn analyzer that catches async/await mistakes at compile time — the kind that are structurally valid C# but semantically dangerous. It's not about style preferences. It's about catching real bugs before they reach production.

Whether you're:

  • A new developer navigating async/await for the first time — the sample project walks through every rule with clear bad/good examples to help you build intuition around async patterns in C#
  • A seasoned developer migrating a codebase from synchronous to async APIs (hello, Redis IDatabase to IDatabaseAsync) and want a safety net that catches every missed await across hundreds of call sites

This package has your back.

Quick Start

dotnet add package MissingAwaitDetector.Analyzers

That's it. Build your project and the analyzers light up immediately.

Package NuGet
MissingAwaitDetector.Analyzers NuGet
MissingAwaitDetector.Analyzers.Abstractions NuGet

The Analyzers package is all you need. The Abstractions package is optional — install it only if you want the [FireAndForget] and [AllowSynchronousIO] suppression attributes.

What It Catches

ID Default What went wrong
MAWT001 Error You used a Task like it was the actual value — string interpolation, ToString(), passing Task<T> where T is expected
MAWT002 Error You called .Result, .Wait(), or .GetAwaiter().GetResult() — blocking the thread and risking deadlocks
MAWT003 Warning You're checking .IsCompleted, .Status, .IsFaulted — polling Task state instead of just awaiting it
MAWT004 Error You called an async method and threw away the Task — unhandled exceptions will crash your app
MAWT005 Error You stored a Task in a variable and never awaited, returned, or composed it
MAWT006 Error You misused a ValueTask — awaited it twice or held onto it too long
MAWT007 Warning Your LINQ .Select() produced a bunch of Tasks that nobody awaited — you wanted await Task.WhenAll(...)
MAWT008 Error You called .Result or .Wait() inside an async method — you're already async, just use await
MAWT009 Warning You stored a Task, poked at it, then returned it — either return it directly or make the method async
MAWT010 Error You used async void — exceptions will crash the process. Use async Task (event handlers are exempt)

Real Examples

These are the kind of bugs that pass code review and waste your afternoon:

// MAWT001 — Looks right, isn't right
var user = GetUserAsync();           // user is Task<UserDto>, not UserDto
return Ok(user);                     // Your API returns a serialized Task object

// MAWT004 — Fire and forget (and crash later)
SaveAuditLogAsync(record);           // Nobody awaited this. If it throws, your app dies.

// MAWT005 — Stored but forgotten
var warmup = CacheWarmupAsync();     // You meant to await this later...
DoOtherStuff();                      // ...but you never did.

// MAWT002 — "I'll just use .Result, what's the worst that can happen?"
var data = GetDataAsync().Result;    // Deadlock on ASP.NET. Have fun.

// MAWT010 — async void is a ticking time bomb
async void OnButtonClick()           // If this throws, your process dies.
{                                    // Use async Task instead.
    await DoWorkAsync();
}

Code Fixes

The analyzer doesn't just complain — it offers one-click fixes via the lightbulb (Ctrl+.) in your IDE.

MAWT001, MAWT002, MAWT005, MAWT008 — "Add await and make method async"

The fixer adds await, marks the method async, and upgrades the return type (voidTask, TTask<T>). It's smart about the specific pattern:

// .Result → await
var data = GetDataAsync().Result;        // BEFORE
var data = await GetDataAsync();         // AFTER

// .Wait() → await
task.Wait();                             // BEFORE
await task;                              // AFTER

// .GetAwaiter().GetResult() → await
var r = GetAsync().GetAwaiter()          // BEFORE
    .GetResult();
var r = await GetAsync();                // AFTER

// Stored but never awaited → await at assignment
var task = GetDataAsync();               // BEFORE (MAWT005)
var task = await GetDataAsync();         // AFTER

If the method is already async, it won't add the keyword again — it just replaces the blocking call with await.

MAWT004 — Fire-and-forget (two options)

Option 1: Add await — adds await and makes the method async:

void M() { ProcessAsync(); }            // BEFORE
async Task M() { await ProcessAsync(); } // AFTER

Option 2: Discard with _ = — acknowledges the fire-and-forget without changing the method signature:

void M() { ProcessAsync(); }            // BEFORE
void M() { _ = ProcessAsync(); }        // AFTER

Suppression Attributes

Sometimes you genuinely mean it. For those cases, install the companion package:

dotnet add package MissingAwaitDetector.Analyzers.Abstractions
using MissingAwaitDetector;

// "Yes, I know this is fire-and-forget. I've handled errors internally."
[FireAndForget("Background telemetry — exceptions are logged and swallowed")]
public async Task UploadTelemetryAsync() { /* ... */ }

// "Yes, I know this blocks. It's a legacy sync interface I can't change yet."
[AllowSynchronousIO("COM interop requirement")]
public int LegacyBridge()
{
    return GetDataAsync().GetAwaiter().GetResult();
}

You can also use explicit discard to acknowledge fire-and-forget:

_ = ProcessAsync();  // "I see you, Task. I'm choosing to ignore you."

A Note on Severity Defaults

All rules ship enabled, and most default to Error. This is intentional — these are the kind of bugs that silently corrupt data or crash apps at 2 AM. I'd rather you see them all upfront and choose which ones to dial back, than have them hiding as suggestions you never notice.

If a rule is too noisy for your codebase, configure it in your .editorconfig:

[*.cs]
# Downgrade to warning
dotnet_diagnostic.MAWT003.severity = warning

# Turn off entirely
dotnet_diagnostic.MAWT009.severity = none

# MAWT003: exclude specific Task properties from inspection warnings
dotnet_diagnostic.MAWT003.excluded_task_properties = Id, CurrentId

See the .editorconfig docs for the full configuration reference.

Patterns the Analyzer Recognizes as Safe

The analyzer isn't trying to fight you. These patterns won't trigger diagnostics:

await task;                           // Awaited — the right thing to do
await task.ConfigureAwait(false);     // ConfigureAwait is fine
Task.WhenAll(task1, task2);           // Composition
Task.WhenAny(task1, task2);           // Racing
return task;                          // Pass-through return
tasks.Add(task);                      // Collecting for later WhenAll
_ = ProcessAsync();                   // Explicit discard — you know what you're doing
async void OnClick(object s, EventArgs e) { } // Event handlers are exempt from MAWT010

Contributing

Found a bug? Have a suggestion? Open an issue.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 83 2/25/2026
1.0.0 94 2/24/2026 1.0.0 is deprecated because it is no longer maintained.