Stell dir aber vor du willst eine IO-Operation (Input, Output) wie z.B. Netwerkzugriffe ala Datenbankabfrage, Web-Request, etc. durchführen so würde bei
var result = service.GetData(...);
UseResult(result);
der ausführende Thread blockiert werden, bis das Ergebnis vorliegt. Das ist nicht ideal.
Daher gibt es asynchrone IO-Vorgänge, bei denen ein Task erzeugt wird und keinen Thread blockiert, sondern bei "Abschluss" des IO-Vorgangs ein sogenannter IO-Thread vom ThreadPool verwendet wird um das "Callback" durchzuführen. Hier eignet sich async/await, da eleganter Code geschrieben werden kann der leserlich ist.
var result = await service.GetDataAsync(...);
UseResult(result);
service.GetDataAsync gibt dabei eine Task zurück, der C#-Compiler erkennt das async/await und erstellt für dich Code der sehr grob analog zu
ich würde lieber auf die Original-Ankündigung verweisen anstatt zu einem Artikel der im Grunde nur die Info zusammenfasst und selbst auch auf diesen Blog-Beitrag von MS verweist.
ein Bug sollte sich im Idealfall durch (Unit-) Tests reproduzieren lassen und falls nicht, so sollte dafür ein Test erstellt werden. Auch damit es später einmal zu keiner Regression kommen kann.
Gibt es jedoch bereits Tests mit passenden Test-Argumenten und der Bug lässt sich dennoch nicht reproduzieren, so mag es u.U. auch am Test-Code selbst liegen.
actual.Should().BeSameAs(actual);
Notiz aus dieser Geschichte:
In solch einem Fall durch bewusste Fehl-Eingaben für den Test, so dass dieser zwangsweise fehlschlagen muss, eine Validierung vom Test-Code vornehmen.
Schlägt der Test nämlich nicht fehl, so ist der Test-Code faul.
Muss es ein multidimensionales Array sein?
"Jagged arrays" werden von der .NET Runtime besser unterstützt, falls Leistung ein Kriterium ist.
Auch bieten die jagged Arrays die Möglichkeit die Matrix als Tupel von Spaltenvektoren zu speichern -- du hast das Layout in der Hand. Für dein Beispiel wäre es dann einfach den Vektor in die jeweilige Spalte zu kopieren.
Es ist auch möglich die Matrix nur in einem (1d) Array zu speichern, so dass einfach Spaltenvektor and Spaltenvektor gehängt wird. Der Zugriff erfolgt dann via entsprechenden Indexer. Grob z.B. so
using System;
int[] col0 = { 1, 2, 3, 4 };
int[] col1 = { 5, 6, 7, 8 };
Matrix matrix = new(4, 2);
matrix.SetColumnVector(col0, 0);
matrix.SetColumnVector(col1, 1);
matrix.Print();
public class Matrix
{
private readonly int[] _storage;
private readonly int _rows;
private readonly int _cols;
public Matrix(int rows, int cols)
{
// Argumente sollten hier validiert werden, auch ist auf potentiellen Overflow
// (durch die Multiplikation) zu achten!
int size = rows * cols;
_storage = new int[size];
_rows = rows;
_cols = cols;
}
// Wir verwenden ein "ref return", es wird also die Referenz zum Matrix-Element
// zurückgegeben. Das erspart uns die Implementierung eines "setters".
public ref int this[int i, int j]
{
get
{
if (i ≥ _rows) throw new ArgumentOutOfRangeException(nameof(i));
if (j ≥ _cols) throw new ArgumentOutOfRangeException(nameof(j));
// Matrix wird spaltenweise (als Tupel von Spaltenvektoren)
// gespeichert
int index = j * _rows + i;
return ref _storage[index];
}
}
public void SetColumnVector(int[] vector, int j)
{
if (vector.Length != _rows) throw new ArgumentException(/* ... */);
int index = j * _rows;
Array.Copy(vector, 0, _storage, index, vector.Length);
}
public void Print()
{
for (int i = 0; i < _rows; ++i)
{
for (int j = 0; j < _cols; ++j)
{
Console.Write($"{this[i, j]}\t");
}
Console.WriteLine();
}
}
}
Aaron Robinson, der Autor dieses Features in .NET, hat https://github.com/AaronRobinsonMSFT/DNNE erstellt und baut auf dem Vorgenannten auf. Hier ist es möglich per UnmanagedCallersOnlyAttribute den nativen Export zu erzeugen, aber nicht in .NET 5 selbst.
danke für den Link zum Artikel. Aber dieser ist wirklich schlecht recherchiert und suggiert dass es genügt UnmanagedCallersOnlyAttribute auf eine statische Methode zu setzen und gut ist es. Das ist bei Weitem nicht so (und der Autor sollte sich besser mit anderen Themen beschäftigen).
UnmanagedCallersOnlyAttribute teilt dem JIT mit, dass die so markierte Methode ausschließlich von nicht-verwaltetem Code (~ nativen Code) aufgerufen wird. Somit wird als "function pointer" (erwähnt auch in voriger Antwort) eine leichtgewichtigere Version erstellt, als wenn die Methode auch von managed Code aufgerufen werden würde.
Ganz trivial ist die Sache aber nicht, da die CLR selbst gehostet werden muss, anders kann JIT-kompilierter IL-Code auch nicht ausgeführt werden.
Die Pakete, die du finden wirst, machen das nicht anders, nur teilweise eben für dich.
Ich finde die zitierte Aussage vom Artikel ein wenig irreführend bzw. verspricht sie mehr als es ist.
Der "klassische Weg" mit nativen Anwendungen zu kommunizieren bleibt dass .NET eine native Bibliothek aufruft und der native Teil per Callback wieder in .NET zurückrufen kann.
Der Callback war bisher ein Delegate, das wird sich aber mit "function pointers" (von C# 9) ändern.
private static string GetXyzInstanceString() // Xyz changed from actual name
{
// We are going to hardcode the value here to 3 (a random number) so that we don't have to
// ...
int id = 3;
return id.ToString(CultureInfo.InvariantCulture);
}
wurde geändert zu
private static string GetXyzInstanceString() // Xyz changed from actual name
{
// We are going to hardcode the value here to 3 (a random number) so that we don't have to
// ...
return "3";
}
die Ursache / Lösung der anderen ist die sauberste Art und Weise das zu handhaben.
Tipp: VS Performance Profiler -> .NET Object Allocation Tracking, dort kannst du die "Wurzeln" betrachten.
Wir haben damit eine Basis, die es uns insgesamt vereinfacht langfristig von phpBB ganz weg zu kommen, woran wir parallel bereits arbeiten.
Zitat
Ob auch ein neues Frontend geplant ist, weiß ich nicht.
Technisch gesehen wird das Frontend neu, basiert wie das Backend auf ASP.NET Core 5 und Razor.
Das Erscheinungsbild bleibt aber (zumindest vorläufig) sehr nahe am bisherigen.
Nähere Infos aber dann wenn es soweit ist.
Und gleich vorweg: es ist dann soweit, wenn wir glauben dass es soweit ist ;-)
Angenommen diese eigenen Vektoren wären structs. Dann ist es auch möglich diese in einen HW-Vector zu lesen. Z.B. (ab .NET Core 3.1):
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
public static class MyVectorExtensions
{
public static Vector256<double> ToVector256(this My3dVector vector)
{
return Unsafe.As<My3dVector, Vector256<double>>(ref vector);
}
}
public struct My3dVector
{
public double X;
public double Y;
public double Z;
}
(Gleiches ginge auch für Vector3)
Unsafe.As<TFrom, TTo> entspricht einem reinterpret_cast in C++. Achtung! Hier ist nicht nur die Klasse Unsafe, sondern auch die Verwendung.
wird Unsafe verwendet, so kann der JIT keine Typprüfungen durchführen
im Beispiel hier kann der Vector256<double> 4 Elemente aufnehmen, die Struct definiert aber nur 3, daher ist das 4. Vector256<double>-Element Müll
wegen letztem Punkt wird über die Grenzen der Struct hinausgelesen und das kann zu einer AccessViolation führen -- es ist zwar sehr unwahrscheinlich dass das tatsächlich passieren mag, aber es soll dennoch darauf hingewiesen werden
Würde ein Vector128<double> verwendet werden, so hätten nur 2 doubles-Platz und somit kann die gesamte Struct nicht gelesen werden.
Diese Art des Speichern von 3d-Vektoren wird Array of Structures genannt und ist eher ungünstig, da entweder zuviel od. zuwenig gelesen wird.
Auch sind etliche Operationen nur unhandlich durchzuführen.
Daher gibt es auch die Möglichkeit der Structure of Arrays, welche diese Nachteile nicht haben und meist günstigere Implementierungen nach sich ziehen.
Allerdings bedeutet das oft auch, dass wesentliche Teile vom Code angepasst werden müssten.
Zitat
Wenn man das als SSE umschreibt
Mit dem verlinkten Artikel musst du aufpassen, denn da wird float verwendet (zu erkennen an der s postfix der Befehlen (s...single, d...double)).
Für double müsste dann AVX (256bit) verwendet werden und da kann es mit dem mischen (shuffle) problematisch werden, denn AVX ist "eigentlich" nur 2xSSE und d.h. ein Shuffle über die "Lanes" geht nur mittels "Permute" ;-) (ich will damit ausdrücken: es wird komplizierter).
Das Kreuzprodukt ist aber (ähnlich wie Matrizenmultiplikation) ohnehin ein schwieriges Beispiel. Aber bei deinen Anwendungen wohl eine sehr häufig verwendet Operation.
RyuJIT ist der aktuelle JIT-Compiler von .NET und wie so gut wie jeder Compiler arbeitet auch dieser in "Phasen". Ein der erste Phasen ist das "Importieren".
Mit dem JitIntrinsicAttribute (bzw. nach dem Umbenenne nur mehr IntrinsicsAttribute) wird dem RyuJIT mitgeteilt, dass der Typ od. die Methode welches so attributiert ist, speziell zu behandeln ist. D.h. es wird nicht der dort angegebene (IL-) Code verwendet, sondern der JIT importiert spezialisierten Code.
Das ist ein Workaround und nicht mehr, mit dem bestimmte Einschränkungen seitens C# / IL umgangen werden.
Ein Beispiel dazu ist die Implementierung von Span<T>.
Aktuell ist es nicht möglich in einer ref struct ein Feld zu haben das ebenfalls eine ref ist (sollte aber mit C# 10 kommen).
Daher wurde mittels eines ByReference diese Beschränkung umgangen und um möglichst effizienten Maschinencode erzeugen zu können benötigt der RyuJIT eine Sonderbehandlung und genau diese wird mit Intrinsic angegeben.
Da Intrinsic ein Implementierungsdetail ist, wurde es auch als internal deklariert.
Das Bestreben vom .NET-Team ist sowohl C# als auch ggf. IL / allgemeiner: die Plattform so weiter zu entwickeln dass dieser Workaround nicht nötig ist. (Da mit C# geschriebener Code wesentlich portabler ist als Spezialisierungen in C++).
Anm.: CoreRT war / ist hierzu ein "Versuchsfeld" das Vieles direkt in C# implementierte und auf native Implementierungen -- sofern möglich -- verzichtet. Teile davon werden nach und nach zu .NET (Core) portiert.
Zitat
IntrinsicAttribute geben sollte, mit dem man Methoden für eine spezielle Optimierung durch den JIT-Kompiler markieren kann
Ich denke das wurde so um 2015 verlautbart und war wohl eher ein Wunsch als Wirklichkeit.
Angelehnt war das vermutlich an die "auto vectorziation" Möglichkeiten moderner nativer Compiler (wie gängige C/C++ Compiler), die aber auch nicht jeden Code behandeln können, sondern eher nur einfachen schleifenbasierten Code durch Abrollen (Unrolling) und Anwendung von SIMD-Registern.
Einem JIT steht wegen dem "just in time" diese Möglichkeit nicht zur Verfügung, da die auto-vectorization zeitintensiv ist und somit in der knappen dem JIT zur Verfügung stehenden Zeit nicht durchführbar ist.
Nun hat der aktuelle RyuJIT ab .NET Core 2.1 die Möglichkeit der "Tiered Compilation", d.h. es wird mehrstufig kompiliert. In Tier-0 gibt es kaum Optimierungen, so dass der Programmstart zügig voranschreiten kann. Wird eine Methode mehrmals aufgerufen, so dass sie "heiß" wird und wenn diese Aufrufe nach der Startphase ist, so wird der Code dieser Methode mit mehr Optimierungen in Tier-1 kompiliert.
Hier gibt es aktuell verschiedene Diskussionen über weitere Möglichkeiten um noch "optimaleren" Code zu generieren. Dazu zählen Profilinformationen die in Tier-0 gesammelt werden, eine weitere Optimierungsstufe mit Tier-2 usw. Konkret ist hier noch nichts.
Für einen hypothetischen Tier-2 könnte auto-vectorization angewandt werden.
Aber wie vorhin erwähnt klappt das eigentlich nur für simple Schleifen recht gut. Sobald Tensoren höherer Stufe behandelt werden, fehlt dem Compiler einfach das Wissen über die genaue Intention was der Programmierer will und wie die Speicherzugriffe, etc. genau zu erfolgen. Um das zu lösen gibt es mMn zwei Möglichkeiten:
eine angepasstere Programmiersprache für Tensor-Rechnung (und / oder Fortran verwenden ;-))
den maschinennahen Teil mit HW-Intrinsics (HW...hardware) selbst programmieren
Auch wenn aktuelle C/C++ Compiler auto-vectorization, etc. können, werden "kritische Bereiche" nach wie vor mit Intrinsic (händisch) programmiert, da dies schon ein großer Schritt nach vorne vom Programmieren in Assembler ist.
C# / .NET wird hier keine Sonderrolle einnehmen können, noch dazu mit "time constraints", welchen der JIT -- unabhängig vom Tiering -- unterliegt.
Das Versehen einer Methode od. eine Types mit einem Attribut ist bei Weitem nicht ausreichend um spezielle Optimierungen zu ermöglichen bzw. würde das Ergebnis wohl nie so gut werden, als wenn der Programmierer, der die Aufgabe (hoffentlich) genau versteht, selbt optimierten Code schreibt. Dazu wird das Thema einfach schnell sehr komplex, da auch die super-skalaren CPUs mit ihren Pipelines, CPU-Caches, jede Menge Latenzen usw. einspielen. Ohne Profiler auf Maschinencode-Ebene und Betrachtem vom erzeugten Maschinencode geht es hier nicht mehr und das kann mühselig werden (außer man empfindet daran Gefallen ;-)).
Ich würde diese Vorstellung als Zukunftsmusik abtun, die in den nächsten Jahren in .NET wohl nicht spielen wird und in nativen Sprachen wohl auch nur leisen Töne von sich geben wird.
Analog dazu gibt es aktuell auch kaum ein Projekt das wirklich GPU-Code aus herkömmlichen Code automatisch erzeugen kann, der "sauber" läuft und an händisch geschriebenen (z.B. CUDA) GPU-Code herankommt.
Der von dir verlinkte Blog-Beitrag über die HW-Intrinsics ist zwar ein Jahr alt, aber vom Inhalt her aktuell.
Die HW-Intrinsics sind Teil der .NET-API-Landschaft und da ändert sich dann nichts mehr (genauso wie ArrayList seit .NET 1.0 immer noch dabei ist).
Ein paar Verwendungsmuster haben sich herauskristallisiert, aber im Wesentlichen passt der Inhalt dieses Blogs.
Zitat
alle wichtigen Methoden in allen relevanten Befehlssätzen neu zu implementieren müssen, scheint mir unverhältnismäßig mehr Aufwand zu sein.
Es gibt 2 Arten von vektor-gestütztem Code in .NET
Vector2, Vector3, Vector3 und Vector<T> aus System.Numerics
Vector128, etc. aus System.Runtime.Intrinsics
Grob gesprochen sind letztere nur Typen, welche dann mittels den Methoden aus Sse2, Avx2, AdvSimd usw. bearbeitet werden.
D.h. auch dass es je nach Zielplattform und CPU-Unterstützung getestet werden muss um so unterschiedliche Code-Pfade haben zu können. Einen Software-Fallback, falls es keine Unterstützung gibt, sollte auch vorhanden sein.
(Außer du weißt dass der Code eh nur auf einer bekannten Plattform z.B. auf einer Intel i7 CPU läuft, dann kannst du diese Test sparen).
Die Typen aus System.Numerics bieten Methoden zum Bearbeiten an und übernehmen die "Unterstützungs"-Test für dich und haben auch einen Software-Fallback implementiert.
Allerdings ist -- zumindest aktuell* -- der erzeugte Maschinencode mit den HW-Intrinsics meist besser. Zudem können mit den HW-Intrinsics, hier am Beispiel von x64 erklärt, bei Vektoren zuerst "AVX-Schritten" (256 bit), dann der Rest in "SSE-Schritten" (128 bit) und der Rest skalar verarbeitet werden, währen die SN-Vektoren nur eine der Vektor-Größen (entweder AVX falls unterstützt von der CPU, sonst SSE) kennen.
Ein Vorteil der HW-Intrinsics ist auch, dass es viele Referenz-Implementierungen für Problem in C++ mit intrinsics gibt und diese sind somit einfacher zu C# zu portieren.
Klar erhöht sich der Aufwand so (fast immens), aber die Ziel-Gruppe der HW-Intrinsics ist auch nicht das breite Publikum sondern eher die Nische, welche "bestmögliche Geschwindigkeit" haben will (und dazu zählen die .NET Innerein selbst).
* es ist geplant die Implementierungen von System.Numerics-Vektoren neu zu implementieren auf Basis der HW-Intrinsics, die erst später Einzug in .NET hielten als die SN-Vektoren die schon länger da sind
Zitat
Da ich viel mit 3D-Grafik zu tun habe, wäre die Kompilierung in CPU-spezifische Befehlssätze sehr sinnvoll.
Schau dir dazu einmal Vector3 und Vector4 genauer an.
Die wurden für solche Szenarien eingeführt und haben viele Anleihen von DirectX-Math (od. wie das genau heißt) genommen.
Diese Vektoren können auch als Felder in eigenen Typen verwendet werden (die HW-Intrinsics Vektoren übrigens auch).
Auf ein Umschreiben / Anpassen deines Codes wirst du da nicht herumkommen (wie erwähnt schaffen das nicht einmal native Compiler die genügend Zeit dafür haben).
Zitat
um eigenen Code JIT-optimieren zu können?
Im Release-Build führt der JIT seit jeher Optimierungen durch.
(Ich weiß aus dem Kontext, dass du nicht danach gefragt hast).
Daher wird auch nicht "einfach so" eine Kopie erstellt.
Das ist eben die Krux dabei, dass beide Threads (der Main-Thread, sowie jener der die Aktion von Task.Run mit einem ThreadPool-Thread ausführt) auf dasselbe Objekt arbeiten und du nicht bestimmen kann wer wann welche Ändeurng zuerst durchführt. Es ist somit durchaus möglich, dass in den Daten deines Objekts Müll entsteht. Das ist die von Abt erwähnte "Race Condition".
Abhilfen gibt es dafür ein paar:
Thread-Synchronisierungen (suche mal danach, da gibt es Lektüre fürs WE -- Vorzugsweise: Basic Synchronization aus Threading in C# / Joseph Albahari)
immutable Typen verwenden, d.h. Typen die nach der Instanzierung nicht mehr geändert werden können und somit gibt es kein Race
(Anm.: das ist mit ein Grund warum mit C#9 Records eingeführt werden, neben dem weniger Tipparbeit für Datenklassen)
asynchron arbeiten, aber ohne gleichzeitige Zugriffe auf das Objekt, z.B. mittels einer Pipeline in der das Objekt von "Station zu Station" gereicht wird
für SQL Server:
Kannst du SQL Server Express im Docker-Container verwenden?
Dann hast du eine richtige DB, Testdaten kannst du einfügen und so ein Image erstellen das zum Testen verwendet werden kann (ev. auch via CI).
Für andere DBMS halt ein anderes Docker-Image verwenden.
EF InMemory ist für einfach Unit-Tests geeignet -- wie schon erwähnt wurde.
SQLite scheitert wenn Schemas verwendet werden, da diese nicht unterstützt werden.
Wenn bei Tests Inserts/Updates durchgeführt werden, achte darauf dass diese in einer Transaktion durchgeführt werden, die dann nach dem Tests zurückgesetzt wird (rollback), damit zwischen den einzelnen Test-Fällen keine Abhängigkeit entsteht.
dazu hab ich (leider) keine Muße / Zeit ;-)
Irgendwie interessant wäre es aber ein Buch der Art "Perlen von myCSharp.de" (o.ä.) zu erstellen, denn hier im Forum gibt es schon sehr viele gute tiefgründige Beiträge, die z.T. auch zeitlos sind.
Zitat
Dann wäre ich nur noch mit Fragen stellen beschäftigt und würde das Forum im Alleingang füllen :D
die verschiedenen Möglichkeiten, die Arbeit in mehreren Threads untereinander zu synchronisieren
Auch wenns alt ist, ist es immer noch korrekt und ein "Klassiker": http://www.albahari.com/threading/
An den grundlegenden Dingen wird sich hier auch in Zukunft nichts ändern, da die Konzepte für CPUs und Speichermodelle sich "hier nicht weiterentwickeln". Das wäre ggf. mächtige Brüche mit der Kompatibilität.
http://joeduffyblog.com/ (v.a. die alten Beiträge) sind hier ebenfalls eine gute Quelle, die teils sehr in die Tiefe gehen -- nicht zuletzt wegen Joe Duffys damaligen Job für das entsprechende .NET Team.
Entsprechend dem Speichermodell (memory model) der CPU / Threads geht es bei volatile genau um die Sichtbarkeit (der Werte).
Z.B. dass der Wert nicht in einem Register gehalten werden darf, und somit "unsichtbar" für andere Threads ist, sondern "sichtbar" im Speicher sein muss. Mit "Speicher" sind hier auch allfällige Cache-Hierarchien gemeint und da geht es erst recht um die Sichtbarkeit (der Änderungen).
Zusätzlich wird beim Lesen ein "acquire fence" und ein "release fence" beim Schreiben gesetzt, so dass andere Lese-/Schreibvorgänge in Bezug auf den "fence" nicht umgeordnet werden dürfen. Auch dabei geht es um die Sichtbarkeit.
(Anm.: die "fences" sind auch Begriffe aus dem Speichermodell).
Vllt. hätte ich in der vorigen Antwort bei Sichtbarkeit den Bezug zum Speichermodell explizit herstellen sollen, denn mit Sichtbarkeit kann auch mehr gemeint sein (wie public, private, etc.) und somit hast du (in dieser Hinsicht) recht ;-)
Zitat
der Compiler bzw. das Runtime-System
Auch die CPU ist dabei betroffen.
Der/die Compiler dürfen beim Optimieren den Code nur so verändern, dass die "fences" von volatile erhalten bleiben.
Die Runtime / Execution Engine / VM müssen auch die "fences" erhalten lassen.
Die CPU muss dafür sorgen, dass in Bezug auf die "fences" die Sichtbarkeit des Speicherbereichs gegeben ist und darf keine Pipeline-Optimierungen vornehmen welche eine "fence" verletzen würden.
Intel x84/x64 ist hier von Haus aus eher streng ("strong memory model"), aber z.B. Arm-Prozessoren sind hier sehr locker ("weak memory model"). Daher ist für plattformübergreifenden Code und Threading die Kenntnis der Speichermodelle -- zu einem bestimmten Grade -- nicht ganz unwesentlich.
Einschub:
Kestrel (ein Server von ASP.NET Core) lief anfänglich nur auf x86*. Da in der Cloud immer mehr Arm64-CPUs Einzug halten, gab es folglich den Wunsch dass Kestrel auch Arm64 unterstützt. Wie sich jeder vorstellen kann sind Webserver sehr nebenläufig und parallelisiert. Ein großer Punkt für die Unterstützung von Arm64 war das "weak memory Model" und die korrekte Handhabung der Speicherzugriffen.
Neben ausführlichen Reviews für die Pull Requests werden zur Überprüfung auch jede Menge "Fuzz Tests" durchgeführt, denn gerade bei "Threading" ist nur durch "Hinschauen" nciht so einfach potentielle Fallstricke erkennen zu können.
* wird nur x86 angegeben, so sind Intel kompatible 32-/64-bit Systeme gemeint.
Andernfalls wird explizit von x86 und x64 od. Arm64 / Arm32, etc. gesprochen.
Zitat
volatile kann eben nur auf "atomare" Datentypen angewendet
Danke dass du das ergänzt, ist aber auch nicht ganz korrekt / präzise (obwohl ich deine Intention dahinter sehr wohl verstehe).
volatile für Datentypen die größer als die Wortbreite der CPU sind machen keinen Sinn, da so das Prinzip von volatile nicht umgesetzt werden kann.
Dass der C#-Compiler hier long, double ausnimmt, liegt darin begründet dass .NET Anwendung plattformunabhängig laufen sollen, d.h. sowohl auf x86 und x64 und auf 32-bit Systemen (x86) sind diese beiden Datentypen nicht atomar.
Daher auch das "fast" oben, denn auf x64 sind double und long atomare Datentypen, dennoch kann volatile nicht darauf angewandt werden.
Gleiches gilt für atomare Werttypen auf die volatile nicht angewandt werden kann. Z.B.
public struct Foo
{
public int A;
}
ist perfekt atomar, dennoch geht volatile nicht.
Als ich meine Antwort verfasste sah ich "atomar" nur in Bezug auf das von mir Zitierte.
Da sehe ich diesen Punkt immer noch als passend an, aber allgemein gesehen ist dieser Punkt zu unpräzise.
Ich bin immer wieder froh, dass es ein Forum gibt welches solche unpräzisen Punkte aufgreift :-)
Hallo Palladin007,
Zitat
Das sind so Details, die ich mir von so einer Lektüre erhofft hätte
Wenn du einen Punkt hast der dich interessiert, so kannst du ja eine Frage im Forum stellen. Irgendwer wird schon eine Antwort schreiben... ;-)
auch wenns in diesem (Unter-) Forum offtopic ist, sollte das nicht unkommentiert stehen bleiben, da es nicht richtig ist.
Sollte das weiterer Diskussion bedürfen, so können wir das auch abteilen.
Zitat
Variablen-Zugriffe scheinbar nicht ganz so atomar sind, wie erst gedacht - aus dem Grund gibt's ja volatile.
Zitat
Die grundlegenden Datentypen wie int, double, float sind in den meisten Programmiersprachen nicht atomar bzw. muss man dafür über andere Typen wie AtomicInteger oder durch volatile dafür sorgen.
volatile hat mit "atomar" nichts zu tun. Das sind zwei verschiedene Dinge -- beide haben ihren Ursprung zwar im "Threading", aber sind dennoch grundverschieden.
volatile ist ein Modifizierer für die Sichtbarkeit der Variable.
"atomar" heißt hier dass der Wert unteilbar (~ Atom) geschrieben / gelesen wird, ohne dass "torn writes / reads" zugelassen werden.
Typen deren Größe der Wortgröße der CPU entspricht werden immer atomar geschrieben / gelesen. sizeof(T) == Wortgröße <=> atomar.
D.h. bei x86 ist int, float atomar. Bei x64 ist double auch atomar.
Referenzen sind -- per Konstruktion -- ebenfalls immer atomar.
Größere Werttypen als Wortgröße der CPU brauchen besondere Behandlung, damit sie atomar verwendet werden können (z.B. durch Interlocked).
So nebenbei: das ist auch der Grund warum Span<T> nur "stack-only" ist, da es ohne Laufzeiteinbußen sonst nicht möglich ist atomare Vorgänge damit abzubilden. Da es eben nur auf dem Stack gestattet ist, gibt es kein Problem mit nebenläufigen Zugriffen.
führe einfach eine Dummy-Abfrage zum Application-Server durch.
Wenn du den Server auch beeinflussen kannst, so z.B. eine "Hallo"-Abfrage, die vom Server die Zeit zurückgibt, etc.
So kannst du prüfen ob die Verbindung passt und auch ob die Authentifizierung / Autorisierung soweit OK ist.
Zitat
wegen blockierter Ports oder sonstiges nicht möglich war
Für so einen Zweck macht das sinn. Generell sind solche Tests aber mit Vorsicht zu genießen, da zwischen Test, der OK liefert, und späterer Verwendung viel passieren kann. D.h. im Betrieb kann einfach der Reqeust zum Server abgefeuert werden und falls es ein Problem gibt via Exception-Handling darauf reagieren, z.B. indem ein neuer Versuch nach kurzer Wartezeit durchgeführt wird (siehe dazu Polly).
Da diese DLL wohl per [DllImport] eingebunden wird, kann man nur per WinAPI-Funktion SetDllDirectory den Suchpfad ändern, s.a. How can I specify a [DllImport] path at runtime? (Wichtig ist, daß dies vor dem Aufruf der ersten Funktion aus dieser DLL passieren muß).
Die Runtime hat natürlich Unterschiede zwischen den verschiedenen Betriebssystemen. Nur als Beispiel wird für Windows das Windows-API verwendet, während *nix auf die entsprechenden POSIX-APIs zugreift.
Durch die Angabe der Ziel-Runtime --runtime win-x64 wird entweder "cross compiled" od. nicht. Daher kann es -- auch beeinflusst von den NuGet-Referenzen -- zu Unterschieden der Build-Ausgabe kommen.
Konkret kann ich mangels Kenntnis des Projekts aber nicht auf die tatsächliche Ursache zurückschließen.
Zitat
dass ein und derselbe Befehl in ein und derselben SDK-Version zu ein und demselben Ergebnis führt
Dem ist auch so -- bis auf den Unterschied dass ab .NET Core 3.0 bei Windows eine PROJECTNAME.exe erzeugt wird, während bei *nix eine PROJECTNAME erzeugt wird.