async programming
C# tasks
ref keyword
asynchronous methods
task management

Ref in async Task

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In C#, you cannot use ref, out, or in parameters on an async method. This is not an arbitrary syntax rule. It exists because async methods are transformed into state machines, and passing by reference into that suspended execution model would create unsafe and confusing lifetime behavior. If you need to return multiple values or mutate shared state, there are better patterns.

Why ref Does Not Work with async

This is invalid:

csharp
1public async Task UpdateAsync(ref int value)
2{
3    await Task.Delay(100);
4    value++;
5}

The compiler rejects it because the async method may suspend and resume later. A by-reference parameter points to storage owned by the caller, and that storage lifetime does not fit cleanly with the generated async state machine.

In short:

  • 'async methods can pause and resume'
  • 'ref expects direct reference semantics to caller-owned storage'
  • the combination is deliberately disallowed

Return a Value Instead

The simplest replacement is to return the updated value.

csharp
1using System.Threading.Tasks;
2
3public static async Task<int> IncrementAsync(int value)
4{
5    await Task.Delay(100);
6    return value + 1;
7}

Call it like this:

csharp
int x = 10;
x = await IncrementAsync(x);

This is usually the cleanest option because the data flow stays explicit.

Return Multiple Values with a Tuple

If the original reason for ref was “I need to update several outputs,” return a tuple.

csharp
1using System.Threading.Tasks;
2
3public static async Task<(int Count, bool Success)> ProcessAsync(int count)
4{
5    await Task.Delay(100);
6    return (count + 1, true);
7}

Usage:

csharp
var result = await ProcessAsync(5);
Console.WriteLine(result.Count);
Console.WriteLine(result.Success);

This is a good replacement for out-style patterns in async code.

Use a Mutable Reference Type When Shared State Is Intentional

If you truly need shared mutable state, wrap it in a class and pass the reference type normally.

csharp
1using System.Threading.Tasks;
2
3public sealed class CounterState
4{
5    public int Value { get; set; }
6}
7
8public static async Task IncrementAsync(CounterState state)
9{
10    await Task.Delay(100);
11    state.Value++;
12}

Usage:

csharp
var state = new CounterState { Value = 10 };
await IncrementAsync(state);
Console.WriteLine(state.Value);

This works because the object reference itself is passed by value, but both caller and callee still see the same object instance.

Be Careful with Shared Mutable State

Just because the mutable-object workaround is possible does not mean it is always a good design. Shared state in async code can create:

  • race conditions
  • ordering bugs
  • unexpected cross-method coupling

If the async method logically computes a result, returning a value is usually better than mutating a shared object.

Use ValueTask or Task the Same Way

The ref restriction is about async, not specifically about Task versus ValueTask. This is still invalid:

csharp
1public async ValueTask DoWorkAsync(ref int value)
2{
3    await Task.Delay(10);
4}

Changing the async return type does not make by-reference parameters legal.

Refactoring Old APIs

If you are migrating synchronous code like this:

csharp
1public void Parse(ref int count, out bool ok)
2{
3    count++;
4    ok = true;
5}

The async version should usually become:

csharp
1public async Task<(int Count, bool Ok)> ParseAsync(int count)
2{
3    await Task.Delay(10);
4    return (count + 1, true);
5}

That keeps the API explicit and async-friendly.

Common Pitfalls

The biggest mistake is trying to fight the compiler and looking for a syntax trick to force ref into an async method. The language intentionally does not support it.

Another issue is replacing ref with a mutable object everywhere, even when returning a value would be much simpler and safer.

Developers also sometimes confuse “reference type” with ref semantics. A class instance can be mutated through its reference, but that is not the same thing as a ref parameter.

Summary

  • 'ref, out, and in parameters are not allowed on async methods in C#.'
  • The restriction exists because async methods are compiled into resumable state machines.
  • Return a value or a tuple instead of trying to emulate ref directly.
  • Use a mutable reference type only when shared state is actually the right design.
  • Prefer explicit async result flow over hidden mutation whenever possible.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.