C
C#4mo ago
Stefanidze

✅ How do async methods handle references

You can't manually declare references (out and ref arguments) in async methods because of how async works (method returns before any work is done). It got me questioning the logic of it, because as far as i know, most of the argument passing in c# uses references, and by default passing an instance of a class uses a reference (except for records) Are all arguments in async methods treated as records and copied into a separate instance to later be used? Or it only affects the manual declaration of pointers? Why?
15 Replies
Anton
Anton4mo ago
Arguments are always passed by value, including references. Physically, there's no such thing as a reference. There are pointers, references are a conceptual thing. Async methods are converted into state machines, with all their context as fields. Ref parameters aren't allowed because ref fields in classes aren't allowed (the state machines are always classes) And references in classes aren't allowed because 1. the GC can't deal with pointers on the GC heap that don't point exactly at other objects on the GC heap 2. they might point to stack memory, so it's to prevent stack references fro escaping onto objects with a longer lifetime
Stefanidze
StefanidzeOP4mo ago
Well aren't most objects passed use a pointer? I'm not asking why ref parameters aren't allowed in async methods, it's just if i do:
public static void Main(){
var _mydata = new MyData();
Test(_mydata);
Console.WriteLine(_mydata.Variable)
}
public static void Test(MyData object){
object.Varible = 10;
}
public class MyData{
public int Variable { get; set; }
}
public static void Main(){
var _mydata = new MyData();
Test(_mydata);
Console.WriteLine(_mydata.Variable)
}
public static void Test(MyData object){
object.Varible = 10;
}
public class MyData{
public int Variable { get; set; }
}
I expect to get a result of 10 (am i wrong here?) So if i do
public async Task Main(){
var _mydata = new MyData();
await Test(_mydata);
Console.WriteLine(_mydata.Variable)
}
public async Task Test(MyData object){
object.Varible = 10;
await Task.CompletedTask;
}
public class MyData{
public int Variable;
}
public async Task Main(){
var _mydata = new MyData();
await Test(_mydata);
Console.WriteLine(_mydata.Variable)
}
public async Task Test(MyData object){
object.Varible = 10;
await Task.CompletedTask;
}
public class MyData{
public int Variable;
}
Will the output be any different?
Kouhai
Kouhai4mo ago
No, it won't be different, it'll be 10
Anton
Anton4mo ago
a pointer is stored in the state machine pointing to the heap allocated object there's no copying involved outside of the pointer itself
Kouhai
Kouhai4mo ago
Just a small note, difference between a reference and a pointer is that, a reference is implemented using a pointer, The GC can move the actual location in memory and the reference would point to the new location.
Stefanidze
StefanidzeOP4mo ago
Well, that's nice to know, still, why managed pointers are not allowed while native pointers work just as fine?
Anton
Anton4mo ago
because you control the memory of native pointers
Kouhai
Kouhai4mo ago
Well, let's say you pass a valuetype like int by ref into an async method What happens now after we return from the method that called the async method? We'd be pointing to stack memory that shouldn't be used
Anton
Anton4mo ago
The GC only knows how to move the pointers that point to the start of an object. If you had a pointer to the middle of an object (a field reference for example) it wouldn't get moved with the object and you got yourself an invalid pointer Also, the GC can't tell pointers at the middle of an object count as references to the object so it's just never allowed even in cases where it provably would be safe
Stefanidze
StefanidzeOP4mo ago
method can return before the underlying async method is completed? and the memory for the pointer will be freed up/disposed before the operation on it is completed
Anton
Anton4mo ago
not disposed or freed up, it's stack memory, it will be used for something else and you int reference will end up looking at completely different stuff unpredictably different UB
Kouhai
Kouhai4mo ago
Yes, it'll return
class Foo
{
Task? Baz;
void Bar()
{
Baz = BarAsync();
}
async Task BarAsync()
{
await Task.Delay(1000);
Console.WriteLine("BarAsync");
}
}
class Foo
{
Task? Baz;
void Bar()
{
Baz = BarAsync();
}
async Task BarAsync()
{
await Task.Delay(1000);
Console.WriteLine("BarAsync");
}
}
When calling Bar, we'll 100% return before BarAsync finishes execution
Stefanidze
StefanidzeOP4mo ago
Ok, i think i understand, because of concurrent execution there may be pointer corruption so no pointers pointing to the values inside the object are allowed, only pointers to objects themselves
Kouhai
Kouhai4mo ago
Yes pretty much
Stefanidze
StefanidzeOP4mo ago
Thanks everyone, i learned a lot of new stuff today :) I guess my question is answered
Want results from more Discord servers?
Add your server