I've been writing things like this in my implementations:
public void SomeMethod(int someValue, List<int> someValues)
{
Task generatedTask = null;
{
int anotherValue = 2;
object valuesRef = someValues;
generatedTask = new Task(delegate{
anotherValue += someValue + GetSum(valuesRef);
Console.WriteLine(anotherValue);
});
}
generatedTask.Start();
}
However, I don't know exactly what's happening here...
Maybe everything was "copied" to the delegate. Or maybe, like reference types, all value types will have a copy associated to the Task
delegate until it exists?
I'm just trying to understand what exactly happens in latest C# versions for performance matters.
These variables are referred to as local because they exist (and are visible) only during the lifetime of the method. They are said to have local scope. When the method ends, the variable goes out of scope and is destroyed. C# divides the world of types into value types and reference types.
A local variable reference in the function or block in which it is declared overrides the same variable name in the larger scope. In programming languages with only two levels of visibility, local variables are contrasted with global variables.
Local Variables Variables defined within a function or block are said to be local to those functions. Anything between '{' and '}' is said to inside a block. Local variables do not exist outside the block in which they are declared, i.e. they can not be accessed or used outside that block.
Nothing physical happens. A typical implementation will allocate enough space in the program stack to store all variables at the deepest level of block nesting in the current function. This space is typically allocated in the stack in one shot at the function startup and released back at the function exit.
Excellent question; captured variables and closure contexts. Decompiling it shows that the current compiler creates 2 capture context objects here:
public void SomeMethod(int someValue, List<int> someValues)
{
Task task;
<>c__DisplayClass3 class2; // <== compiler generated type; unpronounceable
<>c__DisplayClass1 class3; // <== compiler generated type; unpronounceable
class3 = new <>c__DisplayClass1(); // outer-scope context
class3.someValue = someValue;
task = null;
class2 = new <>c__DisplayClass3(); // <== inner-scope context
class2.CS$<>8__locals2 = class3; // <== bind the contexts
class2.anotherValue = 2;
class2.valuesRef = someValues;
task = new Task(new Action(class2.<SomeMethod>b__0));
task.Start();
return;
}
If your objective is to minimise context objects, you could perform the closures manually:
public void SomeMethod2(int someValue, List<int> someValues)
{
Task generatedTask = null;
{
var ctx = new MyCaptureContext();
ctx.anotherValue = 2;
ctx.valuesRef = someValues;
ctx.someValue = someValue;
generatedTask = new Task(ctx.SomeMethod);
}
generatedTask.Start();
}
class MyCaptureContext
{
// kept as fields to mimic the compiler
public int anotherValue;
public int someValue;
public object valuesRef;
public void SomeMethod()
{
anotherValue += someValue + GetSum(valuesRef);
Console.WriteLine(anotherValue);
}
}
You can also avoid a delegate creation per-task by caching a single delegate that passes in the state separately:
public void SomeMethod(int someValue, List<int> someValues)
{
Task generatedTask = null;
{
var ctx = new MyCaptureContext();
ctx.anotherValue = 2;
ctx.valuesRef = someValues;
ctx.someValue = someValue;
generatedTask = new Task(MyCaptureContext.SomeMethod, ctx);
}
generatedTask.Start();
}
class MyCaptureContext
{
// kept as fields to mimic the compiler
public int anotherValue;
public int someValue;
public object valuesRef;
public static readonly Action<object> SomeMethod = SomeMethodImpl;
private static void SomeMethodImpl(object state)
{
var ctx = (MyCaptureContext)state;
ctx.anotherValue += ctx.someValue + GetSum(ctx.valuesRef);
Console.WriteLine(ctx.anotherValue);
}
}
or (cleaner, IMO):
public void SomeMethod(int someValue, List<int> someValues)
{
Task generatedTask = null;
{
var ctx = new MyCaptureContext();
ctx.anotherValue = 2;
ctx.valuesRef = someValues;
ctx.someValue = someValue;
generatedTask = ctx.CreateTask();
}
generatedTask.Start();
}
class MyCaptureContext
{
// kept as fields to mimic the compiler
public int anotherValue;
public int someValue;
public object valuesRef;
public Task CreateTask()
{
return new Task(someMethod, this);
}
private static readonly Action<object> someMethod = SomeMethod;
private static void SomeMethod(object state)
{
var ctx = (MyCaptureContext)state;
ctx.anotherValue += ctx.someValue + GetSum(ctx.valuesRef);
Console.WriteLine(ctx.anotherValue);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With