Während ich den Hund ging, dachte ich an Action<T>
, , async/await
(ja, nerdy, ich weiß ...) und konstruierte ein kleines Testprogramm in meinem Kopf und fragte mich, wie die Antwort sein würde. Ich bemerkte, dass ich mich über das Ergebnis nicht sicher war, also habe ich zwei einfache Tests erstellt.Warum wird eine Klassenbereichsvariable bei Verwendung einer asynchronen Methode erfasst, aber nicht bei Verwendung einer Aktion <T> (Codebeispiele im Inneren)?
Hier ist das Setup:
- ich einen Klassenbereich Variable (string).
- Es wird ein Anfangswert zugewiesen.
- Die Variable wird als Parameter an eine Klassenmethode übergeben.
- Die Methode wird nicht direkt ausgeführt, sondern einer 'Aktion' zugewiesen.
- Bevor die Aktion ausgeführt wird, ändere ich den Wert der Variablen.
Was wäre der Ausgang? Der Anfangswert oder der geänderte Wert?
Ein wenig überraschend, aber verständlich, die Ausgabe ist der geänderte Wert. Meine Erklärung: Die Variable wird nicht auf den Stapel geschoben, bis die Aktion ausgeführt wird, also wird sie geändert.
public class foo
{
string token;
public foo()
{
this.token = "Initial Value";
}
void DoIt(string someString)
{
Console.WriteLine("SomeString is '{0}'", someString);
}
public void Run()
{
Action op =() => DoIt(this.token);
this.token = "Changed value";
// Will output "Changed value".
op();
}
}
Als nächstes habe ich eine Variation:
public class foo
{
string token;
public foo()
{
this.token = "Initial Value";
}
Task DoIt(string someString)
{
// Delay(0) is just there to try if timing is the issue here - can also try Delay(1000) or whatever.
return Task.Delay(0).ContinueWith(t => Console.WriteLine("SomeString is '{0}'", someString));
}
async Task Execute(Func<Task> op)
{
await op();
}
public async void Run()
{
var op = DoIt(this.token);
this.token = "Changed value";
// The output will be "Initial Value"!
await Execute(() => op);
}
}
Hier machte ich DoIt()
Rückkehr ein Task
. op
ist jetzt ein Task
und nicht mehr ein Action
. Die Methode Execute()
erwartet die Aufgabe. Zu meiner Überraschung ist die Ausgabe jetzt "Anfangswert".
Warum verhält es sich anders?
DoIt()
erst Execute()
wird aufgerufen, ausgeführt werden, also warum es den Anfangswert von token
nicht erfassen?
komplette Tests: https://gist.github.com/Krumelur/c20cb3d3b4c44134311f und https://gist.github.com/Krumelur/3f93afb50b02fba6a7c8
In DoIt befindet sich das Capture in averyString, dem Methodenparameter, nicht im Tokenfeld. Da der Wert, den Sie an die Methode übergeben, "Anfangswert" ist, ist dies der Wert, den Sie sehen. –
* DoIt() wird nicht ausgeführt, bis Execute() aufgerufen wird * - nicht wahr. Wenn Sie eine Aufgabe in einem 'asynchronen' Kontext instanziieren, wird sie sofort gestartet - sie wartet nicht, bis sie 'abgewartet' ist (so können Sie mehrere gleichzeitige Aufgaben erwarten). –
@AntP Das ist natürlich richtig - und das weiß ich. Scheint, ich war so verblüfft über das unterschiedliche Verhalten, dass ich es vergessen habe. Mit Blick auf die Upvotes scheint die Frage recht interessant zu sein. – Krumelur