Archief - C# Vermoedelijk threading probleem

Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.

SideShow

Legacy Member
Hallo

Ik ben dus bezig met eenvoudige oefeningen en heb enkele vragen omtrent dit test wip project: Index of /sideshow

Het is een system tray tool die eenvoudig, percentage-gewijs, aangeeft hoeveel je downloadsnelheid bedraagt. (Let wel, de maximale downloadsnelheid is momenteel hard coded, evenals de selectie van de netwerk adapter)

Het compileert en loopt prima, alleen durft het zo heel af en toe eens vast te lopen; het crasht niet maar het reageert en update niet meer echt. Ik vermoed dat het te maken heeft met threading (deadlock?)

Het updaten van de form lukte al helemaal niet, en staat daarom in commentaar.
De bool CheckForIllegalCrossThreadCalls heb ik geprobeerd maar uiteindelijk achterwege gelaten omdat dit een vuile truk blijkt te zijn; én het lost mijn probleem niet op.

Ik vraag niet van mijn probleem op te lossen, maar ik wil gewoon zeker zijn dat de reden inderdaad met threads te maken heeft, en ik mij best daarin eens ga verdiepen. Daar hangen dan wellicht ook events en delegates aan vast om dit tot een goed einde te brengen?

Dus als ik het momenteel goed zie, is de timer een andere thread, en probeert dus in een andere thread controls aan te passen met behulp van mijn refresh method?

Nog enkele punten:

- is dit wel een goede manier van werken? Al die statics enzo?
- ik heb geen gemiddelde functie gevonden en heb dus maar een eigen mini class gebruikt
- ik vermoed dat er een memory leak in zit. (Geheugen gaat iedere seconde met 4K omhoog) Ik dacht eerst aan de refresh functie die telkens een nieuw bitmap object aanmaakt, maar de geheugenlocatie van het vorige object zou toch telkens vrijgegeven moeten worden, gezien niks nog refereert. Sowieso dacht ik als een functie ten einde is, alle variabelen worden vrijgegeven?

cptKangaroo

Legacy Member
Het lijkt mij vooral te liggen aan het gebrek aan opkuisen van je bitmap en graphics objecten. Dat soort disposable objecten moet je altijd met .Dispose() opruimen om lekken te vermijden (of gebruik Using).

Ook creëer je telkens een nieuwe System.Timers.Timer -- die kan je beter expliciet naar je Form doen verwijzen via .SynchronizingObject, en ook opruimen na gebruik. Of een System.Windows.Forms.Timer gebruiken.

Fraggie

Legacy Member
Los van je problemen, zou je die ArrayList beter niet vervangen door een List<double> ?

SideShow

Legacy Member
Merci voor de replies
Het lag niet bepaald aan de bitmaps in de loop ... dit zijn objecten die vroeg of laat door de garbage collector opgeruimd worden, toch?

In ieder geval is de reden van de mysterieuze vastloper, de icon handle op het bitmap object.

Ik heb dit de dagen erna bestudeerd en de reden is omdat die handle blijkbaar "unmanaged" geheugen gebruikt in dit geval (weet niet zeker als een handle dit altijd doet)

Nu, als je info opzoekt over icon objecten in .NET en deze handles, dan gebruiken de "pro's" dus een entry in een user32.dll om het geheugen op te ruimen.

Uiteindelijk ziet het er zo uit (voor de zekerheid toch dispose bijgestoken)

Code:
[COLOR="DarkOrange"][DllImport("user32.dll", EntryPoint = "DestroyIcon")]
static extern int DestroyIcon(IntPtr hIcon);[/COLOR]

static void Refresh(object sender, System.Timers.ElapsedEventArgs e)
{
	Bitmap b = (Bitmap)basicIcon.Clone();
	Graphics g = Graphics.FromImage(b);

	avg.Update();

	// draw yellow bars
	if (avg.Get > 1) g.FillRectangle(Brushes.LawnGreen, 1, 11, 2, 2);
	if (avg.Get > maxSpeed * .07) g.FillRectangle(Brushes.LawnGreen, 4, 9, 2, 4);
	if (avg.Get > maxSpeed * .17) g.FillRectangle(Brushes.LawnGreen, 7, 7, 2, 6);
	if (avg.Get > maxSpeed * .45) g.FillRectangle(Brushes.LawnGreen, 10, 5, 2, 8);
	if (avg.Get > maxSpeed * .85) g.FillRectangle(Brushes.LawnGreen, 13, 3, 2, 10);

	// update icon
	IntPtr handle = b.GetHicon();
	trayIcon.Icon = Icon.FromHandle(handle);
	trayIcon.Text = string.Format("{0} KB/s", avg.Get);

	// update form
	//Debug.WriteLine(speedForm.InvokeRequired.ToString());
	speedForm.SpeedBar.Value = (int)Math.Min(avg.Get, speedForm.SpeedBar.Maximum);
	speedForm.lblSpeed.Text = string.Format("{0} KB/s", avg.Get);

	// clean
	[COLOR="DarkOrange"]DestroyIcon(handle);[/COLOR]
	b.Dispose();
	g.Dispose();
	//GC.Collect();
}

Weg memoryleak, weg freeze. Voorlopig moet ik nog eens uitvissen hoe ik nu precies crossthread ui controls moet aanpakken, maar daar zijn genoeg howto's over te vinden

cptKangaroo

Legacy Member
SideShow zei:
Voorlopig moet ik nog eens uitvissen hoe ik nu precies crossthread ui controls moet aanpakken, maar daar zijn genoeg howto's over te vinden

Delegate voor je Refresh() functie definiëren en een nieuw object van die delegate instantiëren. En wanneer je dan de UI wilt refreshen, dan doe je dat zo:

If JeForm.InvokeRequired
--> ja --> JeForm.Invoke(InstanceOfDelegate)
--> nee --> JeForm.Refresh()​

.BeginInvoke() kan ook, natuurlijk. Als je in die Refresh() functie vanuit meerdere threads tegelijk eenzelfde object aanspreekt, dan doe je best eerst een [sync]lock op dat object.

SideShow

Legacy Member
Blijkt toch niet zo eenvoudig om heel het threading/delegate/event/invoke gedoe te snappen als beginner. Tal van beginnersboeken die 20 bladzijden spenderen aan int, string en simpele dingen, maar de zaken die echt uitleg vragen worden dikwijls kort vermeld.

Ik weet niet hoe het momenteel zit in de A1 toegepaste informatica, maar wij hebben in ieder geval niks daarvan gezien in der tijd.

Er blijkt precies een "gap" te zitten in vele boeken. Delegates en events worden altijd van ver behandeld, maar als je dan eens geavanceerde c# code bekijkt, snap je er de ballen van. Alles komt dan samen ... delegates, interfaces, events, ... Wat ook niet helpt is het Engels gerichte jargon en daarbij nog eens Engelse benamingen van eigen objecten... Daarom heb ik ook de neiging om mijn eigen variabelen/objecten/classes in het Nederlands te benoemen om makkelijk onderscheid te zien.... wat dus ook niet ideaal blijkt als je code wil uitwisselen

cptKangaroo

Legacy Member
Ik vind het zelf makkelijker om Engels te lezen, maar het gaat eigenlijk om de gedachte achter de Delegate techniek: als jij voortdurend informatie moet uitwisselen met iemand, maar je telkens te veel tijd verliest met het plannen van een afspraak en het wachten tot de andere opdaagt, dan kan je beter een afgevaardigde (="délégué") naar dat overleg sturen.

Zo hoeven beide partijen niet rechtstreeks met elkaar te synchroniseren en kunnen ze elk aan hun eigen ritme verder werken en de afgevaardigde/koerier van de andere partij ontvangen wanneer het past in hun eigen werkschema.

NeverwinterX

Legacy Member
SideShow zei:
Blijkt toch niet zo eenvoudig om heel het threading/delegate/event/invoke gedoe te snappen als beginner. Tal van beginnersboeken die 20 bladzijden spenderen aan int, string en simpele dingen, maar de zaken die echt uitleg vragen worden dikwijls kort vermeld.

Ik weet niet hoe het momenteel zit in de A1 toegepaste informatica, maar wij hebben in ieder geval niks daarvan gezien in der tijd.

Er blijkt precies een "gap" te zitten in vele boeken. Delegates en events worden altijd van ver behandeld, maar als je dan eens geavanceerde c# code bekijkt, snap je er de ballen van. Alles komt dan samen ... delegates, interfaces, events, ... Wat ook niet helpt is het Engels gerichte jargon en daarbij nog eens Engelse benamingen van eigen objecten... Daarom heb ik ook de neiging om mijn eigen variabelen/objecten/classes in het Nederlands te benoemen om makkelijk onderscheid te zien.... wat dus ook niet ideaal blijkt als je code wil uitwisselen

In boeken die een overzicht geven van heel C# kan dit het geval zijn. Zoek eens meer specifiek op "C# threading book" op google.

Moto

Legacy Member
K dit is allemaal beetje teveel als ge pas begint, maar om maar een voorbeeld te geven van Rx

Dus declareer van boven een observable stream dat is eigenlijk het omgekeerde van een list ipv dat ik ga zeggen "hier steekt erin" komt hij mij zeggen "hier is
iets voor u" :)
Code:
private static IObservable<double> deltaBytes;

Volgende in de main
Dit is niet hetzelfde als gij had, ken nog niet alles van rx
- Maar dus elke seconde vanaf "Now"
- haal ik de delta op
- Buffer ik de delta's totdat ik er 3 heb
- en dan bereken ik de average derop en geef ik die terug

Code:
deltaBytes = Observable.Timer(DateTimeOffset.Now, TimeSpan.FromSeconds(1))
                                 .Select(x => GetBytesReceivedDelta())
                                 .Buffer(3)
                                 .Select(x => x.Average());

En daarna dit stuk code om de icon aan te passen
Code:
deltaBytes.Subscribe(RefreshAvg);

static void RefreshAvg(double average)
{
   Bitmap b = (Bitmap)basicIcon.Clone()
   ....
}

en ook gebruiken bij de Load van het frm
met de SubscribeOn method kunt ge bepalen op welke thread de subscribe wordt uitgevoerd
Hier in het voorbeeld is de SynchronizationContext.Current de Main UI thread

Code:
      static void frmToolWindow_Load(object sender, EventArgs e)
      {
         deltaBytes.ObserveOn(SynchronizationContext.Current)
                   .SubscribeOn(SynchronizationContext.Current)
                   .Subscribe(avg =>
                     {
                        frmToolWindow.SpeedBar.Value = Math.Min((int)avg, frmToolWindow.SpeedBar.Maximum);
                        frmToolWindow.Text = string.Format(RECEIVING_TEXT, Math.Round(avg, 1));
                     });
      }

Moto

Legacy Member
kon het nie laten :s
Heb het eens veranderd met een image toe te voegen in de resource ipv dat in code te tekenen

meterke.png


en dan percentage bereken van speed en herconvereteren naar een index in de bitmap

Code:
var index = (int)((average * 100 / maxSpeed) / (100 / 5));

als ge dus maar 4 icons hebt opt eind delen door 4 :)

en de Observable voor het formke te updaten meegeven met het formke

code is dan
Code:
       [STAThread]
      static void Main()
      {
         #region Initialize notify icon (system tray icon)
         trayIcon = new NotifyIcon();
         trayIcon.Text = string.Format(RECEIVING_TEXT, 0);
         trayIcon.ContextMenu = new ContextMenu(new MenuItem[] {new MenuItem("Receiving", ChangeView), new MenuItem("Sending", ChangeView), new MenuItem("-"), new MenuItem("Exit", ExitApplication)});
         trayIcon.ContextMenu.MenuItems[0].RadioCheck = true;
         trayIcon.ContextMenu.MenuItems[1].RadioCheck = true;
         trayIcon.ContextMenu.MenuItems[0].Checked = true;
         trayIcon.DoubleClick += (_, __) => frmToolWindow.Visible = !frmToolWindow.Visible;
         trayIcon.Icon = Properties.Resources.speedmeter;
         trayIcon.Visible = true;
         #endregion;

         basicIcon = Properties.Resources.Meterke;
       
         var bufferCount = 3;

         var deltaBytes = Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(1000 / bufferCount))
                                 .Select(x => GetBytesReceivedDelta())
                                 .Buffer(bufferCount)
                                 .Select(x => x.Average());

         frmToolWindow = new ToolWindow(deltaBytes, maxSpeed);

         deltaBytes.Subscribe(RefreshAvg);

         Application.Run();
      }

      public static double GetBytesReceivedDelta()
      {
         return rand.Next(0, maxSpeed);           //Todo calc delta
      }

      static void RefreshAvg(double average)
      {
         var index = (int)((average * 100 / maxSpeed) / (100 / 5));

         var iconSetRect = new Rectangle(1, 1 + (24 * index), 22, 22);

         using (var b = basicIcon.Clone(iconSetRect, basicIcon.PixelFormat))
         {
            // update icon
            trayIcon.Icon = Icon.FromHandle(b.GetHicon());
            trayIcon.Text = string.Format(RECEIVING_TEXT, Math.Round(average, 1));    
         }
      }

      static void ChangeView(object sender, EventArgs e)
      {
         trayIcon.ContextMenu.MenuItems[0].Checked = (sender == trayIcon.ContextMenu.MenuItems[0]);
         trayIcon.ContextMenu.MenuItems[1].Checked = (sender == trayIcon.ContextMenu.MenuItems[1]);
         //Debug.WriteLine(((MenuItem)sender).Text);
         // ... todo
      }

      static void ExitApplication(object sender, EventArgs e)
      {
         Application.Exit();
         trayIcon.Dispose();
      }
   }

En de toolWindow code

Code:
     public partial class ToolWindow : Form
    {
        public ToolWindow(IObservable<double> deltaBytes, int max)
        {
            InitializeComponent();

            this.SpeedBar.Maximum = max;

            // manually set the form's location
            Location = new Point(Screen.PrimaryScreen.Bounds.Width - Width - 120, Screen.PrimaryScreen.Bounds.Height - Height - 100);

            this.Load += (_, __) =>
            {
                deltaBytes.ObserveOn(SynchronizationContext.Current)
                          .SubscribeOn(SynchronizationContext.Current)
                          .Subscribe(avg =>
                            {
                                SpeedBar.Value = Math.Min((int)avg, SpeedBar.Maximum);
                                Text = string.Format(Program.RECEIVING_TEXT, Math.Round(avg, 1));
                            });
            };
        }
        
        private void SpeedForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            // cancel exiting application when form is closed
            if (e.CloseReason == CloseReason.UserClosing)
            {
                e.Cancel = true;
                this.Visible = false;
            }
        }
    }

SideShow

Legacy Member
Thanks all, ik ga dit vandaag eens bekijken, lijkt me reuze interessant
Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.
Terug
Bovenaan