[C#]Events, Threads en delegates

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • TeeDee
  • Registratie: Februari 2001
  • Laatst online: 23:59
Ik heb een Form, een UserControl en een Class waarin het e.e.a. uitgevoerd wordt.
Het usercontrol start de Class in een aparte thread op. Nu wil ik informatie uit de class (bijvoorbeeld in de vorm van "klaar met verwerken") terug geven in een Textbox op het Form.

Form:
C#:
1
2
3
4
5
6
7
8
9
10
public PrintManager()
{
    InitializeComponent();
    UserControl.Notification += new Events.OutPutNotify(UpdateServerOutput);
}
private void UpdateServerOutput(string Notify, bool ClearOutput)
{
    if (ClearOutput) { txtServerOutput.Clear(); }
    txtServerOutput.AppendText(Notify + Environment.NewLine);
}

Events.cs
C#:
1
2
3
4
5
6
7
public class Events
{
    public delegate void OutPutNotify(string Message, bool ClearOutput);
    public Events()
    {
    }
}

UserControl.cs
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public usc_Control()
{
    InitializeComponent();
}
public event Events.OutPutNotify Notification;

Thread startThread;
private void Tester()
{
    WorkThread thread = new WorkThread();
    thread.Run();
}
private void button1_Click(object sender, EventArgs e)
{
    Notification("Processing...",false);
    startThread = new Thread(new ThreadStart(this.Tester));
    startThread.Start();
}

De informatie uit het usercontrol werkt perfect. M.a.w. ik krijg in een textbox op Form1 netjes de tekst Processing... te zien. Alleen om de informatie uit de Class te halen lukt me niet.

Alleen de informatie uit de thread krijg ik niet door.
C#:
1
2
3
4
5
6
7
8
9
usc_Control uiForm;
public WorkThread(usc_Control control)
{
    uiForm = control;
}
public void Run()
{
    uiForm.Invoke(uiForm.Notification, new object[] { "melp", false });
}

Probeer ik in Run() het e.e.a. te invoken, krijg ik
code:
1
The event 'usc_Control.Notification' can only appear on the left hand side of += or -= (except when used from within the type usc_Control')
Deze melding heb ik meerdere malen doorgenomen, alleen begrijp ik er niks van.

Het lijkt mij onlogisch om op bijvoorbeeld het usercontrol het volgende te plaatsen:
C#:
1
2
3
4
5
6
7
8
9
10
public delegate void UpdateOutput(string s, bool o);

public partial class usc_Control: UserControl
{
public UpdateOutput m_UpdateOutput;
public usc_Control()
{
    InitializeComponent();
}
}

Met andere woorden, Events en Methods nog een keer aan usc_Control hangen. Lijkt mij niet de bedoelin.

Ik zit volgens mij enorm te aan te kloten met events en delegates. Eigenlijk is mijn vraag: hoe krijg ik informatie uit de WorkThread terug naar het Form.
is dit toevallig Event Bubbling?

Heart..pumps blood.Has nothing to do with emotion! Bored


Acties:
  • 0 Henk 'm!

  • DrDelete
  • Registratie: Oktober 2000
  • Laatst online: 21:44
had je al gekeken naar de backgroundworker component? Deze biedt versimpelde wijze van updaten van de thread.

Acties:
  • 0 Henk 'm!

  • TeeDee
  • Registratie: Februari 2001
  • Laatst online: 23:59
Dus in het usercontrol met een backgroundworker aan de slag? Volgens mij moet ik dan nog steeds in het UserControl events / delegates aanmaken om deze terug te krijgen op het Form.

Zal er wel even een blik op werpen.

Heart..pumps blood.Has nothing to do with emotion! Bored


Acties:
  • 0 Henk 'm!

  • Webgnome
  • Registratie: Maart 2001
  • Laatst online: 20:41
observer-observable patroon?

Strava | AP | IP | AW


Acties:
  • 0 Henk 'm!

  • riezebosch
  • Registratie: Oktober 2001
  • Laatst online: 10-09 11:15
Volgens mij is hier al heel veel over geschreven...

Canon EOS 400D + 18-55mm F3.5-5.6 + 50mm F1.8 II + 24-105 F4L + 430EX Speedlite + Crumpler Pretty Boy Back Pack


Acties:
  • 0 Henk 'm!

  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

Een listener (uiForm) kan nooit een event van ucs_Control aanroepen. uiForm kan zich alleen 'abonneren' op het Notification event. Je zou eventueel wel OnNotification public kunnen maken en die aanroepen.

Echter zou ik het event zelf verplaatsen naar uiForm en juist ucs_Control de listener maken. Op die manier zouden zelfs meerdere controls op basis van een notificatie aktie kunnen ondernemen.

If it isn't broken, fix it until it is..


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 01:56
Die melding die je krijgt is een compiler error ?

Dat is logisch: aangezien je het keyword 'event' gebruikt, kan de class waarin die event gedefinieerd is, enkel die event gaan 'aanroepen'.

Hoe krijg je informatie terug:
Ik zou de class die je taak uitvoert, zelf de Thread laten starten. (Je kan de Run method zo maken dat die een parameter krijgt of je taak op een andere thread moet uitgevoerd worden of niet, indien wel, start de Run method een thread waar je uw verwerking op doet).
Je Taak heeft dan een aantal events die de taak raised wanneer de verwerking klaar is. Je usercontrol kan zich dan gewoon abboneren op die event.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • TeeDee
  • Registratie: Februari 2001
  • Laatst online: 23:59
riezebosch schreef op maandag 11 februari 2008 @ 10:52:
Volgens mij is hier al heel veel over geschreven...
Veel daarvan gaat over een opzet met een Form en 1 Thread. Voor zover ik kan vinden niet over een Form, UserControl en Thread. De methodiek zal wel hetzelfde zijn, alleen lijkt mij het niet logisch als ik op meerdere plekken een Event moet definiëren.
Niemand_Anders schreef op maandag 11 februari 2008 @ 11:00:
Echter zou ik het event zelf verplaatsen naar uiForm en juist ucs_Control de listener maken. Op die manier zouden zelfs meerdere controls op basis van een notificatie aktie kunnen ondernemen.
Kijk, dat is een suggestie die mij een stuk logischer lijkt. Alleen als ik nu dus meerdere UserControls heb waarbij elk Control een andere Thread (andere Classes en opdrachten) kan opstarten zal ik voor elk Control een apart Event moeten maken? We gaan eens even rommelen.

Heart..pumps blood.Has nothing to do with emotion! Bored


Acties:
  • 0 Henk 'm!

  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

TeeDee schreef op maandag 11 februari 2008 @ 11:13:
[...]
Kijk, dat is een suggestie die mij een stuk logischer lijkt. Alleen als ik nu dus meerdere UserControls heb waarbij elk Control een andere Thread (andere Classes en opdrachten) kan opstarten zal ik voor elk Control een apart Event moeten maken? We gaan eens even rommelen.
Whoami heeft via IsynchonizeInvoke interface reeds eerder een oplossing reeds eerder beschreven. Je hoeft dan niet meer de event handlers zelf te synchoniseren.

If it isn't broken, fix it until it is..


Acties:
  • 0 Henk 'm!

  • TeeDee
  • Registratie: Februari 2001
  • Laatst online: 23:59
Niemand_Anders schreef op maandag 11 februari 2008 @ 11:42:
[...]
Whoami heeft via IsynchonizeInvoke interface reeds eerder een oplossing reeds eerder beschreven. Je hoeft dan niet meer de event handlers zelf te synchoniseren.
Code heb ik inmiddels gevonden, en ik ben er mee bezig. Ik zal daar eens mee aan de slag gaan. Zo op het eerste kom ik er nog niet helemaal uit, voornamelijk hoe ik nu op basis van die code kan abonneren op het event. Maar we gaan vrolijk verder.

[ Voor 14% gewijzigd door TeeDee op 11-02-2008 11:47 ]

Heart..pumps blood.Has nothing to do with emotion! Bored


Acties:
  • 0 Henk 'm!

  • TeeDee
  • Registratie: Februari 2001
  • Laatst online: 23:59
Even een klein schopje: ik heb sinds mijn laatste post nog geen tijd gehad om me goed in te lezen en dergelijke. Ik heb wel het Subscriber pattern van MS (als ik me het pattern goed voor de geest haal is dit eigenlijk het observer pattern) doorgelopen maar nog niet helemaal het e.e.a. goed kunnen verwerken.

Het is is dus niet zo dat ik het opgegeven heb ;)

[ Voor 7% gewijzigd door TeeDee op 12-02-2008 09:00 ]

Heart..pumps blood.Has nothing to do with emotion! Bored


Acties:
  • 0 Henk 'm!

  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

Op http://www.dofactory.com kun je zo'n beetje implementaties van alle design patterns vinden voor C#. Wil je geoptimaliseerde broncode, dan moet je hun kit (75 dollar, of 99 voor zowel VB- als C# versies) kopen.

Ook hebben ze een demo ecommerce applicaties gemaakt waarin ze de patterns hebben verwerkt. Erg leuke kit moet ik zeggen.

If it isn't broken, fix it until it is..


Acties:
  • 0 Henk 'm!

  • TeeDee
  • Registratie: Februari 2001
  • Laatst online: 23:59
Niemand_Anders schreef op dinsdag 12 februari 2008 @ 10:35:
Op http://www.dofactory.com kun je zo'n beetje implementaties van alle design patterns vinden voor C#.
DoFactory heeft inderdaad wel goede samples. Deze had ik al in mijn Bookmarks staan gelukkig ;)

Heart..pumps blood.Has nothing to do with emotion! Bored


Acties:
  • 0 Henk 'm!

  • TeeDee
  • Registratie: Februari 2001
  • Laatst online: 23:59
Hierbij even een kleine update. Ik blijf problemen hebben met deze 'materie'.

Nog eventjes de kale structuur:
Form1
C#:
1
2
3
4
5
6
7
8
9
10
11
public partial class Form1 : Form
{
    private void UpdateServerMessage(string Message)
    {
        textBox1.AppendText(Message);
    }
    public PrintManager()
    {
        InitializeComponent();
    }
}
Het Usercontrol:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public partial class usc_Usercontrol : UserControl
{
    public usc_Usercontrol()
    {
        InitializeComponent();          
    }
    private void Tester()
    {
        ProcessEvents thread = new ProcessEvents();
        thread.Run();
    }
    private void button1_Click(object sender, EventArgs e)
    {
        Tester();
    }
}

ProcessEvents
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ProcessEvents
{
    public ProcessEvents()
    {}  
    public void Run()
    {
        Thread t = new Thread(new ThreadStart(ProcessData));
        t.Start();
    }
    private void ProcessData()
    {
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(200);
        }
    }
}

Na zoeken, proberen en lezen zie ik door de bomen het bos niet meer. Mijn situatie, uitgeschreven, is als volgt: Een Thread wordt vanuit een een User_control opgestart. De opgestarte Thread geeft informatie terug. Deze informatie moet weer terug gegeven worden naar een Form, en niet het User_control.

Met gevonden informatie als dit heb ik het idee dat al deze voorbeelden een Thread opstarten vanuit (in mijn idee) een Form. Volgens mij maken deze allemaal gebruik van het Observer (of Subscriber) pattern.

De optie van Niemand_Anders
Echter zou ik het event zelf verplaatsen naar uiForm en juist ucs_Control de listener maken. Op die manier zouden zelfs meerdere controls op basis van een notificatie aktie kunnen ondernemen.
Moet ik nog verder uitproberen. Hoe de aparte Thread dan aan het Event van Form1 kan komen is mij nog even een raadsel.

Als laatste heb ik nog whoami in "\[C#] button.Show() vanuit anderen Thread" geprobeerd te implenteren. Alleen is het me nog niet duidelijk hoe je dit Event opgooit en ergens anders weer verwerkt / opvangt.

Het allermakkelijkste zou zijn als ik ProcessEvents gewoon in het User_control zou stoppen, maar dat is imo weglopen voor het probleem.

Heart..pumps blood.Has nothing to do with emotion! Bored


Acties:
  • 0 Henk 'm!

  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

Waarom start je de thread niet in het usercontrol. Ik zou juist thead.Run() het begin van de nieuwe thread maken. ProcessEvents heeft dan ook niet de logica nodig om de thread te synchroniseren.
Omdat het usercontrol dan ook de handle naar de nieuwe thread heeft, kan deze eventueel ook eenvoudiger worden gestopt (Abort).


Wat betreft het afvuren van je thread event doe ik meestal via twee stappen. Het usercontrol vuurt zelf het primaire event af. Deze wordt afvangen door Form1. Deze afvang (MessageReceived) vuurt zelf een tweede event af en welke door listeners wordt afgevangen. Het event zou eventueel dus zelfs weer terug kunnen komen bij het user control welke hem initieel afvuurde. Een soort van thread relay dus.

Op deze manier maak je van je form een soort van frontend controller. Je zou het ThreadUpdate event in Form1 weg kunnen laten als MessageReceived verder alle afhandeling doet. In het voorbeeld heb ik een fictief Progress user control welke eigenlijk het event van een ander user control consumeert. Form1 heeft echter wel de mogelijkheid om het event eventueel aan te passen of te filteren.

Ik heb het voorbeeld zou eenvoudig mogelijk gehouden, dus ISynchonizeInvoke is hier niet geimplementeerd. Deze heb je wel nodig omdat het event vanuit een andere thread dan het formulier wordt afgevuurt.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//MessageEventArgs bevat onder andere het bericht dat je wil doorgeven
public delegate void ThreadUpdateHandler(object sender, MessageEventArgs e);
class Usercontrol 
{
  ...
  public event ThreadUpdateHandler ThreadUpdate
  ...
  protected virtual void OnThreadUpdate(string message)
  {
    if (this.ThreadUpdate == null)
      return; //no listeners

    MessageEventArgs e = new MessageEventArgs(message);
    this.ThreadUpdate(this, e);
  }

  private void Tester
  {
    ProcessEvents thread = new ProcessEvents();
    thread.Message += new MessageEventHandler(MessageReceived); //ProcessEvents vuurt bij elke loop het Message event af
    thread.Run(); 
   }
   private void MessageReceived(object sender, string message)
   {
      OnThreadUpdate(message);
   }
}

class Form1
{
  ...
  public event ThreadUpdateHandler ThreadUpdate
  
  public Form1
  {
     this.ThreadUpdate += new ThreadUpdateHandler(WriteMessageToTextbox);
     usrControl.ThreadUpdate += new ThreadUpdateHandler(ControlEventHandler);

     //send event to a custom progress usercontrol
     this.ThreadUpdate += new ThreadUpdateHandler(this.progressControl.UpdateProgress);
  }
  private void ControlEvent(object sender, MessageEventArgs e)
  {
    OnThreadUpdate(this, e);  //just delegate the original event
  }
  
  protected virtual void OnThreadUpdate(object sender, MessageEventArgs e)
  {
    if (this.ThreadUpdate == null)
      return; //no listeners

    MessageEventArgs e = new MessageEventArgs(message);
    this.ThreadUpdate(this, e);
  }

  private void WriteMessageToTextbox(object sender, MessageEventArgs e)
  {
     textBox1.AppendText(e.Message);    
  }
}

If it isn't broken, fix it until it is..


Acties:
  • 0 Henk 'm!

  • EfBe
  • Registratie: Januari 2000
  • Niet online
Je maakt het jezelf moeilijk denk ik. Threading is simpel, totdat je dingen gaat samenvoegen, dan wordt het lastig. Dat moet je dus zien te vermijden.

Bij threading moet je 1 ding goed voor ogen houden: het object dat de thread start, 'bezit' / ownt de thread. Als de thread informatie oplevert, is de owner van de thread de target van die informatie, en niets anders.

Moet de informatie wel ergens anders heen, dan heb je 2 mogelijkheden:
1) de owner geeft het door
2) de thread zet een communicatie op met de target.

In het geval 1) is het niet zo moeilijk: pak een van de synchronization voorbeelden tussen threads en je hebt je oplossing voor het probleem: hoe krijg ik data naar mn owner.

In het geval 2) is het lastiger, en je zit dan veelal naar complexere communicatie te kijken, bv named pipes of andere messaging achtige protocols. DIRECT vanuit jouw thread een ander object aanroepen lijkt leuk, maar DAAR begint juist de ellende: winforms zijn single threaded, en met meerdere threads een ander stukje code aanroepen leidt tot onoverzichtelijkheid.

Dus in jouw geval: probeer de data door te geven aan de user control en van daaruit de informatie weer door te geven aan andere objects. Dit levert op dat je de data van de worker thread (die opgestart wordt door de user control) overgeheveld wordt naar de main thread, waar de user control op draait.

Het probleem zit hem in het feit dat je vanuit je thread een seintje moet geven aan je user control en wel op die manier dat wanneer de workerthread eindigt en klaar is met zn werk, hij de MAIN thread kan notifyen dat er iets is zodat de user control op de main thread code kan uitvoeren, bv een event krijgt.

Dit doe je bv door een callback. In de MSDN is veel documentatie te vinden over dit gebeuren. In de MSDN index (lokaal, is sneller), tik in threading [.NET Framework]. Je krijgt dan een lijst met artikelen in de index, bv best practises en 'about threading'. Als je daar 'using threads and threading' clickt, kom je bv op het artikel 'Creating threads and passing data at start time'. Daarin staat hoe je middels een callback kunt zorgen dat de user control een seintje krijgt en hoe je data kunt passen naar de user control.

Bij winforms is het essentieel dat wat er gebeurt mbt de UI dat dat gebeurt door de code op de main thread. Je krijgt anders hoe dan ook problemen. Dmv de callback kan de thread, wanneer deze klaar is, code op de main thread callen, want de callback (de delegate) is gemaakt door de main thread, en dus code op de main thread. :) Zie het voorbeeld dat bij dat artikel staat.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


Acties:
  • 0 Henk 'm!

  • TeeDee
  • Registratie: Februari 2001
  • Laatst online: 23:59
Niemand_Anders schreef op maandag 25 februari 2008 @ 11:02:
Waarom start je de thread niet in het usercontrol. Ik zou juist thead.Run() het begin van de nieuwe thread maken. ProcessEvents heeft dan ook niet de logica nodig om de thread te synchroniseren.
Omdat het usercontrol dan ook de handle naar de nieuwe thread heeft, kan deze eventueel ook eenvoudiger worden gestopt (Abort).
Dat is op basis van een tip van whoami. Zal er nog eens naar kijken, want dit klinkt logischer. Als de Usercontrol de thread start is deze dus de owner, en zal deze evt. events dus ook makkelijker aan het Mainform kunnen teruggeven.

Wat betreft je voorbeeld: daar gaan we eens mee aan de slag. ISynchronize erbij plaatsen is me nog niet helemaal duidelijk, maar daar kom ik hopelijk vanzelf wel achter als ik de materie beter doorheb.

Edit 14:28: ik heb het idee dat sommige zaken door elkaar heen lopen in jouw voorbeeld?
EfBe schreef op maandag 25 februari 2008 @ 11:17:
Je maakt het jezelf moeilijk denk ik. Threading is simpel, totdat je dingen gaat samenvoegen, dan wordt het lastig. Dat moet je dus zien te vermijden.
Ik heb inderdaad wel een flauw vermoeden dat ik het mezelf te moeilijk aan het maken bent.
Bij threading moet je 1 ding goed voor ogen houden: het object dat de thread start, 'bezit' / ownt de thread. Als de thread informatie oplevert, is de owner van de thread de target van die informatie, en niets anders.
En daar ging/ga ik dus de mist in.
...verdere tips...
Thanks. Hier kan ik voorlopig wel weer mee vooruit.

[ Voor 3% gewijzigd door TeeDee op 25-02-2008 14:28 ]

Heart..pumps blood.Has nothing to do with emotion! Bored


Acties:
  • 0 Henk 'm!

  • TeeDee
  • Registratie: Februari 2001
  • Laatst online: 23:59
ik doe dit toch even in een nieuwe reply, voor de overzichtelijkheid.
Volgens mij heb ik nu het e.e.a werkend. Control namen e.d. zijn aangepast, daar ik door de vele probeersels even het spoor bijster was en opnieuw begonnen ben.
Events.cs
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public delegate void ServerNotify(object sender, NotifyEvents e);
public class NotifyEvents : EventArgs
{
    private string _msg;
    public string Message
    {
        get { return _msg; }
        set { _msg = value; }
    }
    public NotifyEvents()
    { }
    public NotifyEvents(string message)
    {
        Message = message;
    }
}

Workthread.cs
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class WorkThread
{
    uiControl _control;
    public WorkThread(uiControl control)
    {
        _control = control;
    }
    public void Run()
    {
        NotifyEvents n = new NotifyEvents();
        for (int i = 0; i < 100; i++)
        {
            if (_control.InvokeRequired)
            {
                n.Message = i.ToString() + Environment.NewLine; ;
                _control.Invoke(_control.NotifyServer, new object[] {this, n});
            }
            Thread.Sleep(10);
        }
    }   
}

uiControl.cs
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public partial class uiControl : UserControl
{
    public event ServerNotify ServerNotified;
    protected virtual void OnServerNotified(NotifyEvents e)
    {
        if (ServerNotified == null) 
            return;
        ServerNotified(this, e);
    }
    public ServerNotify NotifyServer;
    public uiControl()
    {
        InitializeComponent();
        this.NotifyServer = new ServerNotify(UpdateForm);
    }
    private void UpdateForm(object sender,NotifyEvents msg)
    {
        OnServerNotified(msg);
    }
    private void button1_Click(object sender, EventArgs e)
    {
        WorkThread worker = new WorkThread(this);
        Thread t=new Thread(new ThreadStart(worker.Run));
        t.Start();
    }
}

Form1.cs
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        this.userControl11.ServerNotified += 
            new ServerNotify(uiControl_ServerNotified);
    }
    private void uiControl_ServerNotified(Object sender,NotifyEvents e)
    {
        textBox1.AppendText(e.Message);
    }   
}

Nu moet ik alleen nog in Workthread.cs het ThreadSafe gedeelte van whoami inbouwen volgens mij. Als ik het goed begrijp maak ik nu gebruik van de manier van EfBe
Dus in jouw geval: probeer de data door te geven aan de user control en van daaruit de informatie weer door te geven aan andere objects. Dit levert op dat je de data van de worker thread (die opgestart wordt door de user control) overgeheveld wordt naar de main thread, waar de user control op draait.

[ Voor 11% gewijzigd door TeeDee op 25-02-2008 16:44 ]

Heart..pumps blood.Has nothing to do with emotion! Bored

Pagina: 1