Benutzer mit den meisten Antworten
Einstellen des Coprozessor-Verhaltens (Division durch 0 beim Zoom mit MS Chart)

Frage
-
Moin,
ich kann bei extremer Vergrößerung mit einem sehr kleinen Zoomfenster eine Exception (Overflow wegen Division durch 0) erzeugen.
In Win32-Anwendungen kann man dazu den Coprozessor so einstellen, dass dann ein NAN statt der Exception geliefert wird.
Gibt es solche Einstellungen auch für MS Chart oder .NET? Oder kann man das mit Chart.SuppressExceptions abfangen?
Grüße, Messie
bin neu hier...
Antworten
-
Moin,
irgendwie scheint meine Antwort nicht angekommen zu sein (oder ich habe mal schnell die Tabs zugemacht, in denen noch offene Antworten waren).
Danke schonmal für die Tipps, ich habe jetzt erstmal CursorN.Interval auf 10 E-8 gesetzt. Mal sehen, ob das noch Probleme gibt.
@Frank: an welcher Dezimalstelle die Division durch Null entsteht, hängt auch vom Grafikchipsatz ab. Alte Notebooks mit der alter Onboard-Grafik kann man da schnell aus der Reserve loggen.
Grüße, Messie
bin neu hier...- Als Antwort markiert Messie1 Dienstag, 19. April 2011 15:08
Alle Antworten
-
Hallo Messie,
Das hat nichts mit dem Prozessor zu tun.
.NET-Code wird nicht direkt zu Maschinencode, sondern zu IL (intermediate language) kompiliert, das dann von einer Execution Engine (CLR) ausgeführt wird. Die Division wird dabei durch den OpCode div dargestellt. Wenn man sich die Dokumentation zu DivideByZero ansieht, dann kann man lesen, dass nur vier OpCodes diese Ausnahme auslösen können: div (division), rem (remainder) und ihre nicht signierten Versionen div_u bzw. rem_u. Während Gleitkommaoperationen keine DivideByZero-Ausnahme werfen können, sondern bei der Division durch 0 gemäß IEEE 754-Arithmetik positiv/negativ unendlich oder keine Zahl (NaN) ergeben, werfen ganze Zahlen geteilt durch 0 eine DivideByZero-Ausnahme. Eben dieser Fall (ganzzahlige Operation) muss in Deinem Chart-Code eingetreten sein (wo genau?). Die CLR überprüft bei ganzzahligen Operationen den Nenner, und löst die DivideByZero-Ausnahme aus, wenn dieser gleich 0 ist.
Chart.SuppressExceptions unterdrückt nur unwichtige Ausnahmen, kann aber Ausnahmen nicht abfangen, die die CLR generiert (wäre auch fatal!).
Gruß
Marcel -
Hallo Messie,
Du hast erstmal Recht, in Sprachen wie C++ konnte man (u.a. über Assembler Code) den Ko-Prozessor beeinflussen.
Deine Frage hier: [MS Chart: Fehlerspeicher?] ist ja ein wenig ähnlich. Leider gibt es hier nicht viel mehr Möglichkeiten, als das, was dort als Antwort markiert ist. Eine "Overflow wegen Division durch 0" ist in .NET eine DivideByZeroException, diese werden intern durch IL-Codes wie: "div, div.un, rem und rem.un" ausgelöst, was auf Win32-Ebene dem HRESULT: COR_E_DIVIDEBYZERO (0x80020012) entspricht. Das kann dann im Normalfall auch abgefangen werden (aber normal über die Standard-Exception Struktur [Ref]).
Beim Teilen eines Gleitkommawerts durch null etwa ist das Ergebnis den Gesetzen der IEEE 754-Arithmetik folgend, entweder positiv unendlich, negativ unendlich oder NaN (Not-a-Number). Gleitkommaoperationen lösen niemals Ausnahmen aus.In .NET "kennt" der JIT Compiler den Koprozessor und benutzt ihn! Ausserdem kann man nicht direkt "Inline Assembler" in verwaltetem C# Code benutzen - in Teil-Szenarien über PInvoke. Dennoch wird heutzutage mehr die Richtung Parallelisirung (auf mehrere CPUs) eingeschlagen. Es ist ein Teil des .NET Designs, dass der JIT das IL jeweils optimal für die CLR an die Hardware des Systems anpasst (auch, wenn .NET das wahrscheinlich noch nicht 100% in .NET 4.0 tut - NGen'ed Images mal unberücksichtigt). Du könntest jetzt eine FPU oder andere FPGA Boards meinen, aber letztlich gibt es keine speziellen CPU Instruktionen, um diese anzusprechen. So ist ja auch die GPU oft schneller bei größeren Datenmengen und die GPU kann man von .NET über DirectX Shaders oder PInvoke schon (auch ohne Assembler) ansprechen. WPF/Silverlight nutzen die GPU zum Beispiel ziemlich oft.
Exceptions über SuppressException sind keine unwichtigen Ausnahmen, man nennt sie "nicht schwerwiegend", aber das bedeutet trotzdem, dass ein Setzen auf true ernste Probleme im Quellcode verbergen kann, und ggf. Daten falsch dargestellt werden. Es ist nur ein Eigenschaft, eine Subscription für eine Art Chart-Exception-Ereignis gibt es nicht. Da wird die normale Infrastruktur der .NET Exceptions benutzt, die maximal mit den Methoden aus meinem dem Fehlerspeicher-Thread gehandelt werden können.
[Accessing math coprocessor from C# - Stack Overflow]
ciao Frank -
Hallo Frank,
Exceptions über SuppressException sind keine unwichtigen Ausnahmen, man nennt sie "nicht schwerwiegend", aber das bedeutet trotzdem, dass ein Setzen auf true ernste Probleme im Quellcode verbergen kann, und ggf. Daten falsch dargestellt werden.
SuppressException ist ein Überbleibsel aus alten Versionen der Dundas Chart. Während dort aber dadurch vor allem nicht-kritische Ausnahmen sowie Hinweise an den Entwickler unterdrückt wurden, wird sie im System.Windows.Forms.DataVisualization.Charting-Namensraum weniger als 10 Mal verwendet, meist um InvalidOperationException- sowie ArgumentException-Ausnahmen zu verbergen, die entweder automatisch behebbar sind oder bei sauber programmiertem Code eigtl. gar nicht auftreten dürften (Miskonfiguration). Das Setzen dieser Eigenschaft auf true hat sowieso nur in Release-Versionen einen Sinn, und hat überhaupt keinen Bezug zur DivideByZero-Ausnahme, die Messie beschrieb. Genausowenig wie der Koprozessor. Und das bleibt auch so. Mindestens so lange man keinen Einfluss auf das hat, was die execution engine aus den IL-OpCodes macht.
Gruß
Marcel -
Marcel,
Meine Aussage ist hier in der Doku nachlesbar.
Aber "Teile" Deiner Aussagen sind durchaus richtig.
Ich mache mal EOD, ich möchte da eher Messie helfen.
ciao Frank -
Hmm,
es handelt sich hier um die Zoomfunktion des Charts, so dass ich erstmal keinen Griff sehe, an dem ich ziehen kann. Ließe sich das Charting denn mit VS debuggen? Ich denke, da wird meine Erfahrung noch nicht ausreichen.
Der Weg scheint also eher eine Abschaltung der Zoomfunktion bei einer festzulegenden Grenze zu sein.
Danke, Messie
bin neu hier... -
Hallo Messie,
- Hmm, es handelt sich hier um die Zoomfunktion des Charts,
ja, das hattest Du bereits erwähnt. Interessant wäre ggf. ein minimal reproduzierbares Verhalten / Code.
Etwa so:
public Form1() { InitializeComponent(); for (int i = 0; i < 10; i++) chart1.Series[0].Points.AddXY(i, Funktion(i)); } double Funktion(int x) { return x * 2; } private void btnZoom_Click(object sender, EventArgs e) { chart1.Width = 1000; Width = 1000; chart1.ChartAreas[0].AxisX.ScaleView.Zoom(0, 0); }
- Ließe sich das Charting denn mit VS debuggen? Ich denke, da wird meine Erfahrung noch nicht ausreichen.
ggf. schon. Zum Beispiel:
Visual Studio Menü: Extras /Optionen /Debuggen / Allgemein /Durchlaufen des .NET Framework - Quellcodes aktivieren
Dann unter Debugging/Symbole : RadioButton: "Nur angegebene Symbole" anhaken.
Dann dort mit neu dort erzeugen: System.Windows.Forms.DataVisualization
Mit F11 an geeigneter Stelle kommt man dann "ggf." in den weiteren Quellcode herein.
Es gibt natürlich auch andere Hardcore WinDbg/SOS - Methoden, oder die Assembler Sicht, aber das ist normal für Experten, (etwa für Teile des Charts, die unmanaged implementiert sind).- Der Weg scheint also eher eine Abschaltung der Zoomfunktion bei einer festzulegenden Grenze zu sein.
wäre eine Möglichkeit, aber auch der simple Stacktrace könnte ggf. helfen. Etwa sowas:
[MSChart and Auto X axis [Archive] - CodeGuru Forums]
http://www.codeguru.com/forum/archive/index.php/t-496558.html- bin neu hier...
naja, wir hatten schon das Vergnügen :-) Aber ok.
ciao Frank -
at System.Drawing.Drawing2D.GraphicsPath.IsVisible(PointF pt, Graphics graphics)
at System.Drawing.Drawing2D.GraphicsPath.IsVisible(Single x, Single y)
at System.Windows.Forms.DataVisualization.Charting.Selection.HitTest(Int32 x, Int32 y, Boolean ignoreTransparent, ChartElementType[] requestedElementTypes)
at System.Windows.Forms.DataVisualization.Charting.Selection.EvaluateToolTip(MouseEventArgs e)
at System.Windows.Forms.DataVisualization.Charting.Selection.Selection_MouseMove(Object sender, MouseEventArgs e)
at System.Windows.Forms.DataVisualization.Charting.Chart.OnChartMouseMove(MouseEventArgs e)
at System.Windows.Forms.Control.WmMouseMove(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at EngineEye.Program.Main() in C:\Users\Program.cs:line 18
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.Runtime.Hosting.ApplicationActivator.CreateInstance(ActivationContext activationContext, String[] activationCustomData)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssemblyDebugInZone()
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
Das liefert der Stacktrace. Der Fehler wird als "allgemeiner GDI-Fehler" angezeigt.
Annährend reproduzierbar mit folgendem Code wenn man immer wieder kleiner zoomt. Im unten stehenden Beispiel wird das noch abgefangen und mit dem roten Diagonalkreuz angezeigt, ich bekomme eine Exception (möglicherweise, weil ich noch weitere ChartAreas angekoppelt habe.
public partial class Form1 : Form { double Funktion(int x) { return x * 2; } public Form1() { InitializeComponent(); chart1.SuppressExceptions = false; chart1.ChartAreas[0].CursorX.Interval = 0; chart1.ChartAreas[0].CursorY.Interval = 0; chart1.ChartAreas[0].CursorX.IsUserEnabled = true; chart1.ChartAreas[0].CursorX.IsUserSelectionEnabled = true; chart1.ChartAreas[0].CursorY.IsUserEnabled = true; chart1.ChartAreas[0].CursorY.IsUserSelectionEnabled = true; for (int i = 0; i < 1000; i++) chart1.Series[0].Points.AddXY(i, Funktion(i)); } }
Grüße, Messie
bin neu hier... -
Hallo Messie,
Vielleicht hilft es, wenn wir etwas konkreter werden (um Mißverständnisse auszuräumen):
using System; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; namespace WindowsFormsApplication1 { public partial class Form1 : Form { AxisScaleView scaleView = null; public Form1() { InitializeComponent(); this.scaleView = chart1.ChartAreas[0].AxisX.ScaleView; } private void Form1_Load(object sender, EventArgs e) { Random rand = new Random(DateTime.Now.Millisecond); chart1.Series["Series1"].Points.Clear(); for (int i = 0; i < 1000; i++) { chart1.Series["Series1"].Points.AddXY(rand.Next(0, 100) + rand.NextDouble(), rand.Next(0, 100) + rand.NextDouble()); } // Endbenutzer-Skalierung einschränken (minimaler Ausschnitt = 40), // beinträchtigt nicht die manuelle/programmatische Skalierung chart1.ChartAreas[0].AxisX.ScaleView.MinSize = 40d; // TextBox-Werte setzen (Skalierungs-Min/Max) chart1.ChartAreas[0].RecalculateAxesScale(); textBoxSelectionStart.Text = this.scaleView.ViewMinimum.ToString(); textBoxSelectionEnd.Text = this.scaleView.ViewMaximum.ToString(); // Benutzer kann per UI zoomen this.scaleView.Zoomable = true; chart1.ChartAreas[0].CursorX.IsUserEnabled = true; chart1.ChartAreas[0].CursorX.IsUserSelectionEnabled = true; } private void buttonZoomManually_Click(object sender, EventArgs e) { // Start und Ende auf ViewMinimum/ViewMaximum begrenzen double selectionStart = Math.Min(chart1.ChartAreas[0].AxisX.ScaleView.ViewMinimum, double.Parse(textBoxSelectionStart.Text)); double selectionEnd = Math.Max(chart1.ChartAreas[0].AxisX.ScaleView.ViewMaximum, double.Parse(textBoxSelectionEnd.Text)); this.scaleView.Zoom(selectionStart, selectionEnd); } private void buttonZoomProgrammatically_Click(object sender, EventArgs e) { // Programmatisch zoomen scaleView.Position = 20; scaleView.Size = 2; scaleView.SizeType = DateTimeIntervalType.Number; } } }
Ich habe im obigen Code drei Zoom-Methoden implementiert (skizzenhaft). Könntest Du die eingangs von Dir beschriebene DivideByZero-Ausnahme mit diesem Code reproduzieren?
Gruß
Marcel -
Hallo Messie,
ich habe mal folgenden Code nachvollzogen:
using System.Windows.Forms; namespace ChartDemo { public partial class Form1 : Form { double Funktion(int x) { return x * 2; } public Form1() { InitializeComponent(); chart1.SuppressExceptions = false; chart1.Dock = DockStyle.Fill; chart1.ChartAreas[0].CursorX.Interval = 0; chart1.ChartAreas[0].CursorY.Interval = 0; chart1.ChartAreas[0].CursorX.IsUserEnabled = true; chart1.ChartAreas[0].CursorX.IsUserSelectionEnabled = true; chart1.ChartAreas[0].CursorY.IsUserEnabled = true; chart1.ChartAreas[0].CursorY.IsUserSelectionEnabled = true; for (int i = 0; i < 1000; i++) chart1.Series[0].Points.AddXY(i, Funktion(i)); } } }
ich habe nun ständig kleine Ausschnitte mit der Maus selektiert.
Dabei wurde die Skala teilweise bis zu E-11 klein, aber es entstand kein Fehler.Es könnte also sein, dass Du nicht das aktuelle .NET Framework benutzt?
Du kannst die Auswahl des Ausschnittes zwar beschränken, aber das würde wegen der trotzdem möglichen Verkleinerung (wegen mehrfacher Hintereinander-Auswahl) nichts bringen. Möglich aber zum Beispiel, wenn Du die aktuellen Achsen-Skalierungen beschränkst - es kommt etwas auf den Fehler an.In Deinem StackTrace sagt aus, dass beim Ziehen letztlich die IsVisible(PointF pt, Graphics graphics)
den Fehler erzeugt (wegen der Selection). Diese wird intern auf ein GDI+ Methoden umgeleitet, die intern bei einem fehlerhaften Status im .NET Wrapper durchaus eine GDIP-Exception werfen können.Genauere Aussagen sind für mich erst möglich, wenn das für mich reproduzierbar ist.
ciao Frank -
Hallo Messie,
Du kannst natürlich auf die Benutzerselektion Einfluß nehmen:
private void Form1_Load(object sender, EventArgs e) { //... chart1.SelectionRangeChanging += new EventHandler<CursorEventArgs>(chart1_SelectionRangeChanging); } void chart1_SelectionRangeChanging(object sender, CursorEventArgs e) { if (e.NewSelectionStart < 0.5) e.NewSelectionStart = 0.5; if (e.NewSelectionEnd > 99.5) e.NewSelectionEnd = 99.5; if(e.NewSelectionEnd - e.NewSelectionStart > 10) e.NewSelectionEnd = e.NewSelectionStart + 10 ; }
Gruß
Marcel -
Moin,
irgendwie scheint meine Antwort nicht angekommen zu sein (oder ich habe mal schnell die Tabs zugemacht, in denen noch offene Antworten waren).
Danke schonmal für die Tipps, ich habe jetzt erstmal CursorN.Interval auf 10 E-8 gesetzt. Mal sehen, ob das noch Probleme gibt.
@Frank: an welcher Dezimalstelle die Division durch Null entsteht, hängt auch vom Grafikchipsatz ab. Alte Notebooks mit der alter Onboard-Grafik kann man da schnell aus der Reserve loggen.
Grüße, Messie
bin neu hier...- Als Antwort markiert Messie1 Dienstag, 19. April 2011 15:08
-
Hallo M.,
- Danke schonmal für die Tipps
gern.
- @Frank: an welcher Dezimalstelle die Division durch Null entsteht, hängt auch vom Grafikchipsatz ab.
ja, das ist sicher nochmal wichtig für andere Benutzer später, dass Du das nachvollziehen konntest.
- CursorN.Interval ... gesetzt.
Freut mich, dass es so scheinbar jetzt für Dich funktioniert.
Wenn das Problem mit "CursorN.Interval" gelöst ist, dann hake Dein Hinweis bitte als Antwort ab.
Wenn meine (unsere) Antworten auch hilfreich waren, ggf. dies auch gemäß Deiner Sicht bewerten.
ciao Frank