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
- Fire-and-forget straight to the database.
db.StringSetAsync(key, value, flags: FireAndForget)is ordinary code; this rule is only about waiting for a result you have declined. - A discarded result.
_ = tran.StringSetAsync(...)is exactly what the rule asks for.
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.