Skip to the content.

CommandFlags.FireAndForget means “send this, and do not report back”. The command is queued and sent like any other, but the task you get is already completed, carrying the default value - it is not, and never will be, the server’s answer. Reading it is not an error, it is just always null/0/false.

// flagged - value is always RedisValue.Null, whenever you read it
var tran = db.CreateTransaction();
var value = await tran.StringGetAsync(key, CommandFlags.FireAndForget);
await tran.ExecuteAsync();

// suggested - if you want the result, do not ask for fire-and-forget
var pending = tran.StringGetAsync(key);
await tran.ExecuteAsync();
var value = await pending;

// or, if fire-and-forget is what you meant, discard it
_ = tran.StringSetAsync(key, value, flags: CommandFlags.FireAndForget);
await tran.ExecuteAsync();

Unlike SER305 this does not hang, which is why it is a warning rather than an error, and why it has its own ID: deliberate fire-and-forget code needs to be able to silence this without silencing the rule that says a transaction cannot work.

The only fix offered is discard the queued result. Capturing the task and awaiting it after Execute - which is the fix for SER305 - gains nothing here, because the value does not improve with waiting.

Where it applies

Anywhere the flag is visible at compile time, on a transaction or a batch:

tran.StringGetAsync(key, CommandFlags.FireAndForget)                             // flagged
tran.StringGetAsync(key, CommandFlags.FireAndForget | CommandFlags.DemandMaster) // flagged
tran.StringGetAsync(key, flags | CommandFlags.FireAndForget)                     // flagged - `|` only sets bits
tran.StringGetAsync(key, flags)                                                  // not flagged - unknowable

The third case works because | can only ever add the flag, whatever flags holds at runtime. There is no matching treatment of &/~ to prove the flag absent: that would feed SER305, which is an error, and an error must not rest on a partly-understood expression.

Cases that are deliberately not flagged

Suppressing

Reported as a warning, so TreatWarningsAsErrors builds fail until you act on it or turn it down.

<NoWarn>$(NoWarn);SER306</NoWarn>

or locally:

#pragma warning disable SER306

See also Transactions and SER305.