Async-only APIs
Respire's native network APIs are async-only. Use await for commands, connection
setup, batch execution, and transaction commits. Respire does not provide a second
set of synchronous command methods or a native blocking context.
await using var client = await RespireClient.ConnectAsync("localhost:6379");
var value = await client.GetStringAsync("key");
await client.SetAsync("key", "value");
This keeps one execution model for multiplexed connections, cancellation, and
retries. Blocking a calling thread with .Result, .Wait(), or
.GetAwaiter().GetResult() does not turn asynchronous transport into synchronous
I/O. It can starve the thread pool and can deadlock when continuations need a
blocked synchronization context. ConfigureAwait(false) on a blocking caller
does not make that pattern safe.
Make the calling path asynchronous too: return Task, Task<T>, ValueTask, or
ValueTask<T> as appropriate and await it upstream. Use asynchronous factory
methods instead of doing network I/O in constructors. Move I/O outside lock
bodies; use an asynchronous coordination primitive when mutual exclusion is needed.
Wrapping a blocking call in Task.Run still consumes a thread while waiting.
Synchronous in-memory operations, such as building options, creating a batch, reading a completed queued result, and disposing resources, are separate from network command execution. Server-blocking commands also remain asynchronous for the caller. The optional StackExchange.Redis compatibility adapter has its own documented synchronous compatibility surface; it does not add native sync APIs.
RESP009: await Respire operations
The Respire NuGet package includes this warning and its Await the Respire
operation code fix under analyzers/dotnet/cs. There is no extra analyzer package
to install. Keep analyzer assets enabled on the package reference.
await using var client = await RespireClient.ConnectAsync("localhost:6379");
// RESP009
var value = client.GetStringAsync("key").Result;
client.SetAsync("key", "value").AsTask().Wait();
var other = client.GetStringAsync("other").ConfigureAwait(false).GetAwaiter().GetResult();
After applying the fix inside an async method:
await using var client = await RespireClient.ConnectAsync("localhost:6379");
var value = await client.GetStringAsync("key");
await client.SetAsync("key", "value").AsTask();
var other = await client.GetStringAsync("other").ConfigureAwait(false);
The rule recognizes Task and ValueTask results from methods in the Respire
namespace and its children, including extension methods, and user interfaces
marked [RespireCommands] and their implementing methods. It follows AsTask(), ConfigureAwait(...),
WaitAsync(...), and local initializer aliases that are never reassigned or
passed by reference. It does not trace tasks through fields, parameters, arbitrary
helper methods, or reassignment. This is a targeted diagnostic, not a proof that
all calling code is asynchronous.
The fix preserves ConfigureAwait and WaitAsync arguments. It can add async
to an existing task-returning method or local function when the edited document
compiles without changing its signature. Otherwise, make the containing method
asynchronous and update callers manually. No fix is offered for constructors,
properties, lock bodies, or timed/cancellable Wait(...) overloads: replacing
those calls directly could change their contract. An unrelated compile error in
the document can also prevent the fix from being offered. Fix All is available
for supported sites.
RespirePending<T>.Result is a completed queued-result read, not a task-blocking
API. RESP002 checks that its
batch or transaction has been flushed first; RESP009 does not duplicate that rule.
Configure the warning through .editorconfig if your project needs a different severity:
[*.cs]
dotnet_diagnostic.RESP009.severity = warning