N
EffEm schrieb:
Wenn ich jetzt den Monitor.Exit hinter mein ContSpecAddXY setze schützt der dann auch den chart im GUI Thread? Immerhin bezieht sich der Monitor ja nur auf c.
Aua.
Oh je, es ist also wirklich so schlimm. Das hast du völlig falsch verstanden. Der Monitor schützt nicht das c, sondern das dient dazu verschiedene Sperren zu unterscheiden.
Stell dir Monitor.Enter(c) als eine Schranke vor, durch die nur ein Thread hindurchkommt. Trifft ein anderer Thread auf ein anderes (oder das selbe) Monitor.Enter(c) (mit der selben Objektinstanz, auf die c verweist), dann kommt der da erst durch, wenn der andere am Exit vorbei ist.
In C# benutzt man oft ein Dummy Objekt vom Typ Object dafür, so wie hier. lock(){} ist nur die Verkürzte C# Schreibweise für das Monitor Enter/Exit Paar.
// statements_lock2.cs
using System;
using System.Threading;
class Account
{
private Object thisLock = new Object();
int balance;
Random r = new Random();
public Account(int initial)
{
balance = initial;
}
int Withdraw(int amount)
{
// This condition will never be true unless the lock statement
// is commented out:
if (balance < 0)
{
throw new Exception("Negative Balance");
}
// Comment out the next line to see the effect of leaving out
// the lock keyword:
lock(thisLock)
{
if (balance >= amount)
{
Console.WriteLine("Balance before Withdrawal : " + balance);
Console.WriteLine("Amount to Withdraw : -" + amount);
balance = balance - amount;
Console.WriteLine("Balance after Withdrawal : " + balance);
return amount;
}
else
{
return 0; // transaction rejected
}
}
}
public void DoTransactions()
{
for (int i = 0; i < 100; i++)
{
Withdraw(r.Next(1, 100));
}
}
}
class Test
{
static void Main()
{
Thread[] threads = new Thread[10];
Account acc = new Account(1000);
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(acc.DoTransactions));
threads[i] = t;
}
for (int i = 0; i < 10; i++)
{
threads[i].Start();
}
}
}
[Nachtrag]
Man sieht dem Code nicht an, ob da immer die gleichen Arrays vom Thread an die Oberfläche gegeben werden, dann müssten die vor gleichzeitigem Zugriff geschützt werden.
Oder es werden immer neue Arrays produziert, was einmal ständig Prozessorlast durch das Erzeugen der Arrays und das Abräumen durch den Garbage Collector erzeugt. Viel schwerwiegender ist aber dann die Verwendung von BeginInvoke. Wenn da im Thread wirklich 10 Datensätze pro Sekunde erzeugt werden, aber nur 3 von der Oberfläche abgenommen werden, pumpt das Programm ziemlich schnell den Speicher voll.
[/Nachtrag]
Wahrscheinlich ist das der Grund, wo dein Programm hängt, irgendwas falsch verwendetes aus dem Bereich Threading, Invoke oder Synchronisatzion.
EffEm schrieb:
Das mit dem Beispiel ist sehr interessant, wo genau finde ich das?
Gute Frage, das Chart Control ist erst seit Visual Studio 2010 fest dabei. Für das 2008 SP1 war das ein separater Download. Genauer gesagt mehrere, ein MSChart.exe, ein deutsches Language Pack, eine Hilfedatei und zwei zip-Dateien namens WinSamples und WebSamples.
Allein WinSamples enthält laut Überschrift 200 Codebeispiele.
Hier zumindest die Codeschnipsel zu den beiden zitierten Beispielen
using System.Windows.Forms.DataVisualization.Charting;
...
private Thread addDataRunner;
private Random rand = new Random();
private System.Windows.Forms.DataVisualization.Charting.Chart chart1;
public delegate void AddDataDelegate();
public AddDataDelegate addDataDel;
...
private void RealTimeSample_Load(object sender, System.EventArgs e)
{
// create the Adding Data Thread but do not start until start button clicked
ThreadStart addDataThreadStart = new ThreadStart(AddDataThreadLoop);
addDataRunner = new Thread(addDataThreadStart);
// create a delegate for adding data
addDataDel += new AddDataDelegate(AddData);
}
private void startTrending_Click(object sender, System.EventArgs e)
{
// Disable all controls on the form
startTrending.Enabled = false;
// and only Enable the Stop button
stopTrending.Enabled = true;
// Predefine the viewing area of the chart
minValue = DateTime.Now;
maxValue = minValue.AddSeconds(120);
chart1.ChartAreas[0].AxisX.Minimum = minValue.ToOADate();
chart1.ChartAreas[0].AxisX.Maximum = maxValue.ToOADate();
// Reset number of series in the chart.
chart1.Series.Clear();
// create a line chart series
Series newSeries = new Series( "Series1" );
newSeries.ChartType = SeriesChartType.Line;
newSeries.BorderWidth = 2;
newSeries.Color = Color.OrangeRed;
newSeries.XValueType = ChartValueType.DateTime;
chart1.Series.Add( newSeries );
// start worker threads.
if ( addDataRunner.IsAlive == true )
{
addDataRunner.Resume();
}
else
{
addDataRunner.Start();
}
}
private void stopTrending_Click(object sender, System.EventArgs e)
{
if ( addDataRunner.IsAlive == true )
{
addDataRunner.Suspend();
}
// Enable all controls on the form
startTrending.Enabled = true;
// and only Disable the Stop button
stopTrending.Enabled = false;
}
/// Main loop for the thread that adds data to the chart.
/// The main purpose of this function is to Invoke AddData
/// function every 1000ms (1 second).
private void AddDataThreadLoop()
{
while (true)
{
chart1.Invoke(addDataDel);
Thread.Sleep(1000);
}
}
public void AddData()
{
DateTime timeStamp = DateTime.Now;
foreach ( Series ptSeries in chart1.Series )
{
AddNewPoint( timeStamp, ptSeries );
}
}
/// The AddNewPoint function is called for each series in the chart when
/// new points need to be added. The new point will be placed at specified
/// X axis (Date/Time) position with a Y value in a range +/- 1 from the previous
/// data point's Y value, and not smaller than zero.
public void AddNewPoint( DateTime timeStamp, System.Windows.Forms.DataVisualization.Charting.Series ptSeries )
{
double newVal = 0;
if ( ptSeries.Points.Count > 0 )
{
newVal = ptSeries.Points[ptSeries.Points.Count -1 ].YValues[0] + (( rand.NextDouble() * 2 ) - 1 );
}
if ( newVal < 0 )
newVal = 0;
// Add new data point to its series.
ptSeries.Points.AddXY( timeStamp.ToOADate(), rand.Next(10, 20));
// remove all points from the source series older than 1.5 minutes.
double removeBefore = timeStamp.AddSeconds( (double)(90) * ( -1 )).ToOADate();
//remove oldest values to maintain a constant number of data points
while ( ptSeries.Points[0].XValue < removeBefore )
{
ptSeries.Points.RemoveAt(0);
}
chart1.ChartAreas[0].AxisX.Minimum = ptSeries.Points[0].XValue;
chart1.ChartAreas[0].AxisX.Maximum = DateTime.FromOADate(ptSeries.Points[0].XValue).AddMinutes(2).ToOADate();
chart1.Invalidate();
}
/// Clean up any resources being used.
protected override void Dispose( bool disposing )
{
if ( (addDataRunner.ThreadState & ThreadState.Suspended) == ThreadState.Suspended)
{
addDataRunner.Resume();
}
addDataRunner.Abort();
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
...
using System.Windows.Forms.DataVisualization.Charting;
...
private Random random = new Random();
private int pointIndex = 0;
...
private void timerRealTimeData_Tick(object sender, System.EventArgs e)
{
// Define some variables
int numberOfPointsInChart = 200;
int numberOfPointsAfterRemoval = 150;
// Simulate adding new data points
int numberOfPointsAddedMin = 5;
int numberOfPointsAddedMax = 10;
for(int pointNumber = 0; pointNumber < random.Next(numberOfPointsAddedMin, numberOfPointsAddedMax); pointNumber++)
{
chart1.Series[0].Points.AddXY(pointIndex + 1, random.Next(1000, 5000));
++pointIndex;
}
// Adjust Y & X axis scale
chart1.ResetAutoValues();
// Keep a constant number of points by removing them from the left
while(chart1.Series[0].Points.Count > numberOfPointsInChart)
{
// Remove data points on the left side
while(chart1.Series[0].Points.Count > numberOfPointsAfterRemoval)
{
chart1.Series[0].Points.RemoveAt(0);
}
// Adjust X axis scale
chart1.ChartAreas["Default"].AxisX.Minimum = pointIndex - numberOfPointsAfterRemoval;
chart1.ChartAreas["Default"].AxisX.Maximum = chart1.ChartAreas["Default"].AxisX.Minimum + numberOfPointsInChart;
}
// Invalidate chart
chart1.Invalidate();
}
...
Das zweite Beispiel, dass einen Timer statt Invoke verwendet, wäre der Weg, den ich für deine Anwendung auch vorgeschlagen hätte. Sprich ein Workerthread sammelt in einer Schleife Daten und legt sie, threadsicher, irgendwo ab, alte Daten überschreibt er dabei. In der Oberfläche läuft ein Timer, der holt die Daten, threadsicher, ab.
Wo und ob das heute auf MSDN steht, weiß ich leider nicht.
[Edit]
Da
http://archive.msdn.microsoft.com/mschart