Betrachten wir diese sehr einfache asynchrone Methode:
static async Task myMethodAsync()
{
await Task.Delay(500);
}
Wenn ich dies mit VS2013 (Pre Roslyn Compiler) kompiliere, ist die generierte Zustandsmaschine eine Struktur.
private struct <myMethodAsync>d__0 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
Wenn ich es mit VS2015 (Roslyn) kompiliere, lautet der generierte Code wie folgt:
private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
Wie Sie sehen können, generiert Roslyn eine Klasse (und keine Struktur). Wenn ich mich richtig erinnere, haben die ersten Implementierungen der Unterstützung für asynchrone / warten im alten Compiler (CTP2012, denke ich) auch Klassen generiert, und dann wurde sie aus Leistungsgründen in struct geändert. (In einigen Fällen können Sie das Boxen und die Heap-Zuweisung vollständig vermeiden…) (Siehe hier )
Weiß jemand, warum dies in Roslyn wieder geändert wurde? (Ich habe kein Problem damit, ich weiß, dass diese Änderung transparent ist und das Verhalten von Code nicht ändert, ich bin nur neugierig)
Bearbeiten:
Die Antwort von @Damien_The_Unbeliever (und dem Quellcode :)) imho erklärt alles. Das beschriebene Verhalten von Roslyn gilt nur für den Debug-Build (und dies ist aufgrund der im Kommentar erwähnten CLR-Einschränkung erforderlich). In Release wird auch eine Struktur generiert (mit allen Vorteilen davon ..). Dies scheint also eine sehr clevere Lösung zu sein, um sowohl Bearbeiten als auch Fortfahren und eine bessere Leistung in der Produktion zu unterstützen. Interessantes, danke für alle, die teilgenommen haben!
quelle
async
Methoden haben fast immer einen echten asynchronen Punkt - einenawait
, der eine Kontrolle ergibt, für die die Struktur ohnehin eingerahmt werden müsste. Ich glaube, Strukturen würden den Speicherdruck nur fürasync
Methoden verringern , die zufällig synchron ausgeführt wurden.Antworten:
Ich hatte keine Vorkenntnisse darüber, aber da Roslyn heutzutage Open Source ist, können wir den Code nach einer Erklärung durchsuchen.
Und hier in Zeile 60 des AsyncRewriter finden wir:
Obwohl die Verwendung von
struct
s eine gewisse Anziehungskraft hat, wurde der große Gewinn, Edit und Continue innerhalb vonasync
Methoden arbeiten zu lassen, offensichtlich als die bessere Option gewählt.quelle
Es ist schwer, eine endgültige Antwort auf so etwas zu geben (es sei denn, jemand aus dem Compilerteam kommt vorbei :)), aber es gibt ein paar Punkte, die Sie berücksichtigen können:
Der Leistungsbonus von Strukturen ist immer ein Kompromiss. Grundsätzlich erhalten Sie Folgendes:
Was bedeutet das im Wartefall? Naja eigentlich ... nichts. Es gibt nur einen sehr kurzen Zeitraum, in dem sich die Zustandsmaschine auf dem Stapel befindet. Denken Sie daran, dass a
await
effektiv ausgeführt wirdreturn
, sodass der Methodenstapel stirbt. Die Zustandsmaschine muss irgendwo aufbewahrt werden, und das "irgendwo" ist definitiv auf dem Haufen. Die Stapellebensdauer passt nicht gut zu asynchronem Code :)Abgesehen davon verstößt die Zustandsmaschine gegen einige gute Richtlinien zum Definieren von Strukturen:
struct
s sollte höchstens 16 Byte groß sein - die Zustandsmaschine enthält zwei Zeiger, die allein die 16-Byte-Grenze für 64-Bit sauber ausfüllen. Abgesehen davon gibt es den Staat selbst, so dass er die "Grenze" überschreitet. Das ist kein großer Sache, da es höchstwahrscheinlich immer nur als Referenz übergeben wird. Beachten Sie jedoch, dass dies nicht ganz zum Anwendungsfall für Strukturen passt - eine Struktur, die im Grunde genommen ein Referenztyp ist.struct
s sollte unveränderlich sein - nun, das braucht wahrscheinlich keinen großen Kommentar. Es ist eine Zustandsmaschine . Auch dies ist keine große Sache, da die Struktur automatisch generierter Code und privat ist, aber ...struct
s sollte logisch einen einzelnen Wert darstellen. Dies ist hier definitiv nicht der Fall, aber das ergibt sich bereits aus einem veränderlichen Zustand.Und das alles natürlich in einem Fall, in dem es keine Schließungen gibt. Wenn Sie Einheimische (oder Felder) haben, die das
await
s durchlaufen , wird der Status weiter aufgeblasen, was die Nützlichkeit der Verwendung einer Struktur einschränkt.Angesichts all dessen ist der Klassenansatz definitiv sauberer, und ich würde keine merkliche Leistungssteigerung erwarten, wenn ich
struct
stattdessen a verwende. All beteiligten Objekte haben ähnliche Lebensdauer, so dass der einzige Weg , die Gedächtnisleistung zu verbessern wäre , um alle von ihnenstruct
s (Speicher in einem gewissen Puffer, zum Beispiel) - die im allgemeinen Fall natürlich unmöglich ist. Und die meisten Fälle, in denen Sie verwenden würdenawait
zuerst verwenden würden (dh einige asynchrone E / A-Arbeiten), betreffen bereits andere Klassen - zum Beispiel Datenpuffer, Zeichenfolgen ... Es ist eher unwahrscheinlich, dass Sieawait
etwas42
tun , das einfach zurückkehrt, ohne etwas zu tun Heap-Zuweisungen.Am Ende würde ich sagen, dass der einzige Ort, an dem Sie wirklich einen echten Leistungsunterschied sehen würden, Benchmarks sind. Und die Optimierung für Benchmarks ist, gelinde gesagt, eine dumme Idee ...
quelle