.
Anmeldung | Registrieren | Hilfe | Posteingang
Suchen
Home Foren News Member Offers Termine Developer Blogs Knowledge Base

Navigation

Navigationslinks überspringen.
Knowledge Base reduzierenKnowledge Base
Tutorials reduzierenTutorials
Webentwicklung
Cliententwicklung
Datenbankentwicklung
IT Professional
Sharepoint
Sprachspezifisch reduzierenSprachspezifisch
C#
Visual Basic
C++
XAML
SQL
JavaScript
Erfahrungsberichte reduzierenErfahrungsberichte
Entwicklersoftware
Bücher
FAQ Grundlagen

Verknüpfungen

  • Knowledge Base durchsuchen
  • Hilfe zur Knowledge Base
  • RSS Feed
  • Twitter

Reactive Extensions und Windows Forms Controls

Control Invokation mit Rx

Revision 2 dieses Artikels. Alle Kommentare die vor dem 16.10.2010 geschrieben wurden, sind obsolet.
Änderungen:

  • Eine Version nach Anregungen von Mike Bild wurde hinzugefügt.
  • Meine ursprüngliche Version wurde überarbeitet.

Ich habe mich verliebt. Sie heißt “Reactive Extensions” oder kurz Rx und stammt aus den Microsoft DevLabs.
Nun möchte ich mit diesem Artikel aber nicht das ganze “Getting started” durchgehen. Ich empfehle allen, die noch nie mit Rx gearbeitet haben, diese siebenteilige Artikelreihe von Lee Campbell.

Warum dieser KB-Artikel?

In allen Tutorials und Samples, die mir bislang begegnet sind, hängen die Generierung eines Observables und die Subskription immer direkt zusammen. Für Lehrzwecke mag das ganz praktisch sein, weil so die Zusammenhänge deutlich hervortreten. Ich denke aber, das entspricht nicht dem Bild im realen Einsatz von Rx. In der Praxis wird es üblicherweise so sein, dass eine Komponente ein oder mehrere Observables erzeugt, die dann von einer oder mehreren anderen Komponenten abonniert werden. Um so ein Szenario zu demonstrieren, kam mir mein Demo zum SynchronizationContext gerade recht. Das wunderbare an Rx ist ist nämlich auch (unter anderen) dass die Observables den SynchronizationContext quasi gleich eingebaut haben!

Ein weiterer Grund für diesen Artikel ist ein Trick, den ich nirgendwo im Internet gefunden habe (was nicht bedeuten muss dass es ihn nicht doch irgendwo gibt). Jedenfalls meine ich den TimerHook. Mehr dazu am Ende des Codeabschnitts.

Die Anforderung:

Eine LED (Picturebox-Background) soll blinken wenn auf einen Start/Stop-Button geklickt wird und wieder damit aufhören wenn noch einmal auf den Button geklickt wird. Damit der Button funktioniert muß der Farbwechsel in einem eigenen Thread laufen. Löse das Problem mit dem Reactive Extensions Framework (Rx).

Control invokation mit Rx

Form1.cs (Version Rainer Hilmer)

using System;

using System.Concurrency;

using System.Linq;

using System.Threading;

using System.Windows.Forms;

 

namespace UsingRx

{

   public partial class Form1 : Form

   {

      private readonly ControlScheduler ledColdScheduler;

      private readonly ControlScheduler ledhotScheduler;

      private readonly ControlScheduler ledHotConnectableScheduler;

      private bool connectHot;

      private bool connectHotConnectable;

      private readonly SynchronizationContext syncContext;

 

      public Form1()

      {

         InitializeComponent();

         FormBorderStyle = FormBorderStyle.Fixed3D;

         MaximizeBox = false;

         SizeGripStyle = SizeGripStyle.Hide;

         ledColdScheduler = new ControlScheduler(LedCold);

         ledhotScheduler = new ControlScheduler(LedHot);

         ledHotConnectableScheduler = new ControlScheduler(LedHotConnectable);

         connectHot = false;

         syncContext = SynchronizationContext.Current;

         ColorWorker.Ho_impulse += Process_ho_stream_promotion;

         ColorWorker.Hco_impulse += Process_hco_stream_promotion;

      }

 

      private void ButtonColdClick(object sender, EventArgs e)

      {

         ColorWorker.Trigger = !ColorWorker.Trigger;

         var stream = ColorWorker.ColdColorStream;

         stream.SubscribeOn(Scheduler.NewThread)

            .ObserveOn(ledColdScheduler)

            .Subscribe(color => LedCold.BackColor = color);

      }

 

      private void ButtonHotClick(object sender, EventArgs e)

      {

         connectHot = !connectHot;

         ColorWorker.HotColorStream.SubscribeOn(Scheduler.NewThread)

            .ObserveOn(ledhotScheduler)

            .TakeWhile(_ => connectHot)

            .Subscribe(color => { LedHot.BackColor = color; });

 

         // Wird durch den Einsatz von RefCount nicht benötigt:

         //stream.Connect();

      }

 

      private void ButtonHotConnectableClick(object sender, EventArgs e)

      {

         connectHotConnectable = !connectHotConnectable;

         var stream = ColorWorker.HotConnectableColorStream;

         stream.SubscribeOn(Scheduler.NewThread)

            .ObserveOn(ledHotConnectableScheduler)

            .TakeWhile(_ => connectHotConnectable)

            .Subscribe(color => { LedHotConnectable.BackColor = color; });

         stream.Connect();

      }

 

      private void Process_ho_stream_promotion(string streamElement)

      {

         syncContext.Post(state =>

                          textBoxHoStream.Text = state.ToString(),

                          streamElement);

      }

 

      private void Process_hco_stream_promotion(string streamElement)

      {

         syncContext.Post(state =>

                          textBoxHcoStream.Text = state.ToString(),

                          streamElement);

      }

 

      private void Form1FormClosing(object sender, FormClosingEventArgs e)

      {

         ColorWorker.Trigger = false;

         ColorWorker.Ho_impulse -= Process_ho_stream_promotion;

         ColorWorker.Hco_impulse -= Process_hco_stream_promotion;

         Thread.Sleep(500);

      }

   }

}


ColorWorker.cs (Version Rainer Hilmer)

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Linq;

using System.Threading;

 

namespace UsingRx

{

   internal static class ColorWorker

   {

      internal static event Action<string> Hco_impulse;

      internal static event Action<string> Ho_impulse;

 

      internal static bool Trigger;

 

      internal static IObservable<Color> ColdColorStream

      {

         get

         {

            return Observable.Create<Color>(

               color =>

               {

                  while(Trigger)

                  {

                     color.OnNext(Color.LimeGreen);

                     Thread.Sleep(500);

                     color.OnNext(Color.BlueViolet);

                     Thread.Sleep(500);

                  }

                  return () => { };

               });

         }

      }

 

      internal static IObservable<Color> HotColorStream

      {

         get

         {

            var timerHook = Observable.Interval(TimeSpan.FromMilliseconds(500));

            return Observable.Create<Color>(

               color =>

               {

                  timerHook.Subscribe(x => color.OnNext(x % 2 != 0

                                                           ? Color.LimeGreen

                                                           : Color.BlueViolet));

                  return () => { };

               })

               // Note: Just for demonstration --->|

               .Do(color => Promote_ho_stream(color.ToString()))

               //|<---

               .Publish()

               .RefCount();

         }

      }

 

      internal static IConnectableObservable<Color> HotConnectableColorStream

      {

         get

         {

            var timerHook = Observable.Interval(TimeSpan.FromMilliseconds(500));

            return Observable.Create<Color>(

               color =>

               {

                  timerHook.Subscribe(x => color.OnNext(x % 2 != 0

                                                           ? Color.LimeGreen

                                                           : Color.BlueViolet));

                  return () => { };

               })

               // Note: Just for demonstration --->|

               .Do(color => Promote_hco_stream(color.ToString()))

               //|<---

               .Publish();

         }

      }

 

      private static void Promote_ho_stream(string streamElement)

      {

         if(Ho_impulse != null)

            Ho_impulse(streamElement);

      }

 

      private static void Promote_hco_stream(string streamElement)

      {

         if(Hco_impulse != null)

            Hco_impulse(streamElement);

      }

   }

}


Anmerkungen

PromoteStream

Dieses Demo zeigt dass hier tatsächlich ein "HotConnectableStream" erzeugt wird. Auch wenn die LED gestoppt wurde, läuft der Stream weiter. Das Impulse-Event feuert bei jedem neuen Impuls des Streams. Das Event wird in dem Form abonniert und ein Eventhandler schreibt dort den aktuellen Wert in eine TextBox.

Cold Stream, Hot Stream und Hot connectable Stream, die Unterschiede.

Cold Stream

Ein cold stream läuft nur auf Anfrage.

Hot Stream

Ein hot stream läuft ständig, stellt seine Arbeit aber ein wenn es keine Observer mehr gibt.

Hot connectable Stream

Ein hot connectable Stream läuft ständig, auch wenn es keine Observer gibt.

Ho_impulse und Hco_impulse

Bei dem Einsatz der Reactive Extensions werden im Prinzip keine Events benötigt. Da in diesem Demo aber auch demonstriert wird, was mit den Observables passiert wenn keine Observer mehr existieren, habe ich diese beiden Events als Sonden in den Stream gehängt. Mit ihnen transportiere ich Stream-Impulse (ich nenne das jetzt mal so) zu entsprechenden Textboxen.

TimerHook

Eine besondere Erwähnung verdient der TimerHook. Was hat es damit auf sich?
Die Interval-Methode gibt immer nur eine Zahl vom Typ long zurück. Das ist nicht veränderbar. Da ich nach einem Interval aber etwas anderes zurückgeben will (hier Color), bediene ich mich eines Tricks:
Ich generiere innerhalb der gleichen Methode ein weiteres Observable (Observable.Create<Color>). Darin "subscribe" ich das TimerInterval und erzeuge mit jedem Timer-Impuls einen Farb-Impuls. Das Color-Observable ist das was die Methode zurück gibt, nicht das Interval-Observable. Dieses dient nur als Trigger.


 

Form1.cs (Version Mike Bild)

 

using System;

using System.Concurrency;

using System.Drawing;

using System.Linq;

using System.Threading;

using System.Windows.Forms;

 

namespace RxDemo_VersionMikeBild

{

   public partial class Form1 : Form

   {

      private readonly ControlScheduler ledColdScheduler;

      private readonly ControlScheduler ledHotScheduler;

      private readonly ControlScheduler ledHotConnectableScheduler;

      private readonly SynchronizationContext syncContext;

      private bool enableColdObservable;

      private bool enableHotObservable;

      private bool enableHotConnectableObservable;

 

 

      public Form1()

      {

         InitializeComponent();

         FormBorderStyle = FormBorderStyle.Fixed3D;

         MaximizeBox = false;

         SizeGripStyle = SizeGripStyle.Hide;

         ledColdScheduler = new ControlScheduler(LedCold);

         ledHotScheduler = new ControlScheduler(LedHot);

         ledHotConnectableScheduler = new ControlScheduler(LedHotConnectable);

         syncContext = SynchronizationContext.Current;

         ColorWorker.Ho_impulse += Process_ho_stream_promotion;

         ColorWorker.Hco_impulse += Process_hco_stream_promotion;

         SwitchColdStreamObserver();

         SwitchHotStreamObserver();

         SwitchHotConnectableStreamObserver();

      }

 

      private void SwitchColdStreamObserver()

      {

         IObservable<Color> coldObservable =

            ColorWorker.ColdColorStream

               .SubscribeOn(Scheduler.NewThread)

               .ObserveOn(ledColdScheduler)

               .TakeWhile(c => enableColdObservable);

 

         Observable.FromEvent<EventArgs>(buttonCold, "Click")

            .Subscribe(@event =>

                       {

                          enableColdObservable = !enableColdObservable;

                          coldObservable.Subscribe(

                             color => { LedCold.BackColor = color; });

                       });

      }

 

      private void SwitchHotStreamObserver()

      {

         var stream = ColorWorker.HotColorStream;

         stream.SubscribeOn(Scheduler.NewThread)

            .ObserveOn(ledHotScheduler);

 

         Observable.FromEvent<EventArgs>(buttonHot, "Click")

            .Subscribe(@event =>

            {

               enableHotObservable = !enableHotObservable;

               stream.TakeWhile(c => enableHotObservable)

                  .Subscribe(color =>

                  { LedHot.BackColor = color; });

            });

      }

 

      private void SwitchHotConnectableStreamObserver()

      {

         var stream = ColorWorker.HotConnectableColorStream;

         stream.SubscribeOn(Scheduler.NewThread)

            .ObserveOn(ledHotConnectableScheduler);

         stream.Connect();

 

         Observable.FromEvent<EventArgs>(buttonHotConnectable, "Click")

            .Subscribe(@event =>

                       {

                          enableHotConnectableObservable =

                             !enableHotConnectableObservable;

                          stream.TakeWhile(c => enableHotConnectableObservable)

                             .Subscribe(color =>

                             { LedHotConnectable.BackColor = color; });

                       });

      }

 

      private void Process_ho_stream_promotion(string streamElement)

      {

         syncContext.Post(state =>

                          textBoxHoStream.Text = state.ToString(),

                          streamElement);

      }

 

      private void Process_hco_stream_promotion(string streamElement)

      {

         syncContext.Post(state =>

                          textBoxHcoStream.Text = state.ToString(),

                          streamElement);

      }

 

      private void Form1FormClosing(object sender, FormClosingEventArgs e)

      {

         ColorWorker.Ho_impulse -= Process_ho_stream_promotion;

         ColorWorker.Hco_impulse -= Process_hco_stream_promotion;

         Thread.Sleep(500);

      }

   }

}


 

ColorWorker.cs (Version Mike Bild)

 

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Linq;

 

namespace RxDemo_VersionMikeBild

{

   public static class ColorWorker

   {

      internal static event Action<string> Hco_impulse = delegate { };

      internal static event Action<string> Ho_impulse = delegate { };

 

      internal static IObservable<Color> ColdColorStream

      {

         get

         {

            var source = new[] { Color.LimeGreen, Color.Red }

           .Repeat().Share();

            return Observable.Interval(TimeSpan.FromMilliseconds(500))

               .Select(i => source.First());

         }

      }

 

      internal static IObservable<Color> HotColorStream

      {

         get

         {

            var source = new[] { Color.LimeGreen, Color.Red }

           .Repeat().Share();

 

            return Observable.Interval(TimeSpan.FromMilliseconds(500))

               .Select(i => source.First())

               // Note: Just for demonstration --->|

               .Do(color => Promote_ho_stream(color.ToString()))

               //|<---

               .Publish()

               .RefCount();

         }

      }

 

      internal static IConnectableObservable<Color> HotConnectableColorStream

      {

         get

         {

            var source = new[] { Color.LimeGreen, Color.Red }

           .Repeat().Share();

 

            return Observable.Interval(TimeSpan.FromMilliseconds(500))

               .Select(i => source.First())

               // Note: Just for demonstration --->|

               .Do(color => Promote_hco_stream(color.ToString()))

               //|<---

               .Publish();

         }

      }

 

      private static void Promote_ho_stream(string streamElement)

      {

         Ho_impulse(streamElement);

      }

 

      private static void Promote_hco_stream(string streamElement)

      {

         Hco_impulse(streamElement);

      }

   }

}


Form1.Designer.cs (Version Rainer Hilmer)

Anmerkung: In der Version von Mike Bild entfallen die Button-Eventhandler, der Rest ist identisch.

namespace UsingRx

{

   partial class Form1

   {

      /// <summary>

      /// Required designer variable.

      /// </summary>

      private System.ComponentModel.IContainer components = null;

 

      /// <summary>

      /// Clean up any resources being used.

      /// </summary>

      /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>

      protected override void Dispose(bool disposing)

      {

         if(disposing && (components != null))

         {

            components.Dispose();

         }

         base.Dispose(disposing);

      }

 

      #region Windows Form Designer generated code

 

      /// <summary>

      /// Required method for Designer support - do not modify

      /// the contents of this method with the code editor.

      /// </summary>

      private void InitializeComponent()

      {

         this.buttonCold = new System.Windows.Forms.Button();

         this.LedCold = new System.Windows.Forms.PictureBox();

         this.LedHot = new System.Windows.Forms.PictureBox();

         this.buttonHot = new System.Windows.Forms.Button();

         this.label1 = new System.Windows.Forms.Label();

         this.label2 = new System.Windows.Forms.Label();

         this.label3 = new System.Windows.Forms.Label();

         this.LedHotConnectable = new System.Windows.Forms.PictureBox();

         this.buttonHotConnectable = new System.Windows.Forms.Button();

         this.textBoxHcoStream = new System.Windows.Forms.TextBox();

         this.textBoxHoStream = new System.Windows.Forms.TextBox();

         ((System.ComponentModel.ISupportInitialize)(this.LedCold)).BeginInit();

         ((System.ComponentModel.ISupportInitialize)(this.LedHot)).BeginInit();

         ((System.ComponentModel.ISupportInitialize)(this.LedHotConnectable)).BeginInit();

         this.SuspendLayout();

         //

         // buttonCold

         //

         this.buttonCold.Location = new System.Drawing.Point(161, 13);

         this.buttonCold.Name = "buttonCold";

         this.buttonCold.Size = new System.Drawing.Size(93, 23);

         this.buttonCold.TabIndex = 0;

         this.buttonCold.Text = "START/STOP";

         this.buttonCold.UseVisualStyleBackColor = true;

         this.buttonCold.Click += new System.EventHandler(this.ButtonColdClick);

         //

         // LedCold

         //

         this.LedCold.BackColor = System.Drawing.SystemColors.ControlDarkDark;

         this.LedCold.Location = new System.Drawing.Point(260, 18);

         this.LedCold.Name = "LedCold";

         this.LedCold.Size = new System.Drawing.Size(16, 16);

         this.LedCold.TabIndex = 1;

         this.LedCold.TabStop = false;

         //

         // LedHot

         //

         this.LedHot.BackColor = System.Drawing.SystemColors.ControlDarkDark;

         this.LedHot.Location = new System.Drawing.Point(260, 46);

         this.LedHot.Name = "LedHot";

         this.LedHot.Size = new System.Drawing.Size(16, 16);

         this.LedHot.TabIndex = 3;

         this.LedHot.TabStop = false;

         //

         // buttonHot

         //

         this.buttonHot.Location = new System.Drawing.Point(161, 42);

         this.buttonHot.Name = "buttonHot";

         this.buttonHot.Size = new System.Drawing.Size(93, 23);

         this.buttonHot.TabIndex = 2;

         this.buttonHot.Text = "START/STOP";

         this.buttonHot.UseVisualStyleBackColor = true;

         this.buttonHot.Click += new System.EventHandler(this.ButtonHotClick);

         //

         // label1

         //

         this.label1.AutoSize = true;

         this.label1.Location = new System.Drawing.Point(70, 18);

         this.label1.Name = "label1";

         this.label1.Size = new System.Drawing.Size(85, 13);

         this.label1.TabIndex = 4;

         this.label1.Text = "Cold Observable";

         //

         // label2

         //

         this.label2.AutoSize = true;

         this.label2.Location = new System.Drawing.Point(12, 78);

         this.label2.Name = "label2";

         this.label2.Size = new System.Drawing.Size(143, 13);

         this.label2.TabIndex = 5;

         this.label2.Text = "Hot connectable Observable";

         //

         // label3

         //

         this.label3.AutoSize = true;

         this.label3.Location = new System.Drawing.Point(74, 47);

         this.label3.Name = "label3";

         this.label3.Size = new System.Drawing.Size(81, 13);

         this.label3.TabIndex = 8;

         this.label3.Text = "Hot Observable";

         //

         // LedHotConnectable

         //

         this.LedHotConnectable.BackColor = System.Drawing.SystemColors.ControlDarkDark;

         this.LedHotConnectable.Location = new System.Drawing.Point(260, 78);

         this.LedHotConnectable.Name = "LedHotConnectable";

         this.LedHotConnectable.Size = new System.Drawing.Size(16, 16);

         this.LedHotConnectable.TabIndex = 7;

         this.LedHotConnectable.TabStop = false;

         //

         // buttonHotConnectable

         //

         this.buttonHotConnectable.Location = new System.Drawing.Point(161, 73);

         this.buttonHotConnectable.Name = "buttonHotConnectable";

         this.buttonHotConnectable.Size = new System.Drawing.Size(93, 23);

         this.buttonHotConnectable.TabIndex = 6;

         this.buttonHotConnectable.Text = "START/STOP";

         this.buttonHotConnectable.UseVisualStyleBackColor = true;

         this.buttonHotConnectable.Click += new System.EventHandler(this.ButtonHotConnectableClick);

         //

         // textBoxHcoStream

         //

         this.textBoxHcoStream.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;

         this.textBoxHcoStream.Location = new System.Drawing.Point(282, 76);

         this.textBoxHcoStream.Name = "textBoxHcoStream";

         this.textBoxHcoStream.ReadOnly = true;

         this.textBoxHcoStream.Size = new System.Drawing.Size(100, 20);

         this.textBoxHcoStream.TabIndex = 9;

         //

         // textBoxHoStream

         //

         this.textBoxHoStream.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;

         this.textBoxHoStream.Location = new System.Drawing.Point(282, 45);

         this.textBoxHoStream.Name = "textBoxHoStream";

         this.textBoxHoStream.ReadOnly = true;

         this.textBoxHoStream.Size = new System.Drawing.Size(100, 20);

         this.textBoxHoStream.TabIndex = 10;

         //

         // Form1

         //

         this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);

         this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;

         this.ClientSize = new System.Drawing.Size(407, 114);

         this.Controls.Add(this.textBoxHoStream);

         this.Controls.Add(this.textBoxHcoStream);

         this.Controls.Add(this.label3);

         this.Controls.Add(this.LedHotConnectable);

         this.Controls.Add(this.buttonHotConnectable);

         this.Controls.Add(this.label2);

         this.Controls.Add(this.label1);

         this.Controls.Add(this.LedHot);

         this.Controls.Add(this.buttonHot);

         this.Controls.Add(this.LedCold);

         this.Controls.Add(this.buttonCold);

         this.Name = "Form1";

         this.Text = "Control invokation mit Rx";

         this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1FormClosing);

         ((System.ComponentModel.ISupportInitialize)(this.LedCold)).EndInit();

         ((System.ComponentModel.ISupportInitialize)(this.LedHot)).EndInit();

         ((System.ComponentModel.ISupportInitialize)(this.LedHotConnectable)).EndInit();

         this.ResumeLayout(false);

         this.PerformLayout();

 

      }

 

      #endregion

 

      private System.Windows.Forms.Button buttonCold;

      private System.Windows.Forms.PictureBox LedCold;

      private System.Windows.Forms.PictureBox LedHot;

      private System.Windows.Forms.Button buttonHot;

      private System.Windows.Forms.Label label1;

      private System.Windows.Forms.Label label2;

      private System.Windows.Forms.Label label3;

      private System.Windows.Forms.PictureBox LedHotConnectable;

      private System.Windows.Forms.Button buttonHotConnectable;

      private System.Windows.Forms.TextBox textBoxHcoStream;

      private System.Windows.Forms.TextBox textBoxHoStream;

   }

}

von Rainer Hilmer, 09.10.2010 zugeordnet zu keiner Kategorie .

Kommentare

Cool. Sehr schöner Artikel. Ich bin seit langem auch ein großer Rx und natürlich EBC Fan. ;-) Ich hab zu Deinem Beispiel und Deinem, "Hack-Trick" noch ein paar kleine Ideen. Vielleicht auch so: var source = new[] { Color.LimeGreen, Color.BlueViolet }.Repeat().Share(); var observableResult = Observable.Interval(TimeSpan.FromMilliseconds(500)).Select(i => source.First()); Meine kleine Demo-Konsole: using (observableResult.Subscribe(x => Console.WriteLine(x.ToString()))) { Console.ReadKey(); } Funzt dann mit Publish() und Publish().RefCount() natürlich auch Connected mit und ohne Subscriber Reference-Counter. Kleiner Tipp: Schreibe deine Delegaten immer event Action<string> Ho_impulse = delegate {}; dann brauchst Du deine lästige Null-Prüfung beim Invoke nicht mehr. Dein Demo ist aber vor allem sehr cool, da es wie ich finde die UI-Thread-Synchronisierung mit Rx auch noch einmal gut klar macht. Auf die schnelle bin ich durch Dein Observabe Dispose noch nicht durchgestiegen, was machst Du da und warum so genau? Thumbs up und Danke, Mike
von MikeBild, 12.10.2010.

Hallo Mike, herzlichen Dank für den netten Kommentar. Deinen Code prüfe ich gerade. Mir waren Repeat und Share bis jetzt nicht bekannt. Leider lässt die Rx-Doku noch sehr zu wünschen übrig.
Zu deinem Tip bezgl Event: Ich hab diese Prüfung auf null nur eingesetzt weil es halt gängige Praxis ist. Persönlich verzichte ich lieber darauf. Der Grund: Wenn ein Abo vergessen wurde, läuft die App mit einer NullReferenceException vor die Wand. So vermeide ich Flüchtigkeitsfehler bei der Entwicklung. ;-) Ich hatte bei einer EBC-Architektur mal so einen Fall. Hab mich dann gewundert warum das Signal nicht am Ziel ankommt. Kein Wunder, wenn man irgendwo in der Kette ein Glied vergisst. Ohne Nullprüfung fällt es dagegen sofort auf.
von Rainer Hilmer, 12.10.2010.

Ach ja, du hattest wegen dem Dipose gefragt. Mit diesem Disposing melde ich den Client vom Stream ab, um die Unterschiede im Fortlauf der verschiedenen Streams zu demonstrieren.
von Rainer Hilmer, 12.10.2010.

Hallo Reiner, ja mit Rx muss man, mangels Doku, viel spielen. Wichtiger als Repeat() und Share() ist allerdings das Select(..) aus meinem Code. Hier hattest Du auf deinen Hack mit geschachtelten Observables verwiesen. Nicht so optimal wie ich finde. Das ist durch die Map-Funktion Select() unnötig. Zum Event Tip: Eine Null-Prüfung ist auf jeden Fall korrekt - keine wäre noch schlimmer. Hatte ich schon erwähnt, dass ich NullReferenceExceptions hasse :-). Der Tip war - initialisiere den Multicast-Delegate einfach immer mit einem Delegaten. Und schon brauchst Du keine Null-Prüfung mehr. Der Multicast-Delegate ist immer korrekt initialisiert. Zum Event allgemein würde ich sagen, dass dieser beim Einsatz von Rx so und so komplett entfallen kann. Mit Rx hab ich ja ein gutes Publisher/Subscriber Pattern. Damit werden Multicast-Delegaten ein bisschen hinfällig. Ich brauche diese nur noch aus UI Technologiekompatibilitätsgründen :-) (Kettenwort). Nun, die Mischung (Rx+EBC) machst. Klar, dass ein Dispose ein Unsubscribe auslöst - meine Frage geht eher in Richtung - Warum dieser umständliche Weg mit Status-Flags-Variablen "connectHot", Disposable Collection und Dispose? Spannender wäre doch mit bsw. Observable.FromEvent(...), von UI Delegaten/Events direkt auf Observables zu mappen und dieses ganze Status-gehalte und Event-Subscribing ganz außen vor zu lassen., oder? Somit erfüllst Du einen guten Code-Design Zweck - Funktionen mit weniger Nebeneffekten. Nebeneffekte sind immer schlecht und Linq und Rx helfen auf angenehme Art dieses Ziel besser zu erreichen. Aber entschuldige, ich schweife ab und bleib bei - Cooler Artikel. Danke und Gruß, Mike
von MikeBild, 12.10.2010.

Danke Mike. Ich erkenne dass ich noch viel über Rx zu lernen habe.
von Rainer Hilmer, 12.10.2010.

Nachtrag
1. Die Events Ho_Impulse und Hco_Impulse sind nach meiner Ansichtr nötig. Schließlich müsste ich diese Informationen sonst über einen Observer holen. Ich will aber doch gerade demonstrieren was passiert, wenn es keine Observer mehr gibt.
2. Zur Zeit habe ich keine Idee wie das Schalten der Streams über FromEvent funktionieren soll. Kannst du ein Beispiel geben?
3. Ist es nicht umständlicher wenn ich ein Event erst in ein Observable umwandele, um es dann zu verarbeiten?
von Rainer Hilmer, 13.10.2010.

zu 1.) Der Event ist völlig unnötig. Wer Witz an Obsevable ist ja, dass diese gerade wie Events auch die Information an die Subscriber pushen. Vom Observable mus nichtsgeholt werden. Push statt Pull, das ist wichtig. zu 2.) ganz einfach: var enableColdObservable = false; var coldObservable = ColorWorker.ColdColorStream .SubscribeOn(Scheduler.NewThread) .ObserveOn(ledColdScheduler) .TakeWhile(c => enableColdObservable); Observable.FromEvent<EventArgs>(buttonCold, "Click") .Subscribe(e => { enableColdObservable = !enableColdObservable; coldObservable.Subscribe(color => { LedCold.BackColor = color; })); }); zu 3.) Ist es nicht. Mit FromEvent ganz einfach. Ein Observable hat einen entscheidenen Vorteil. Es bietet komplexe Computations an. Das geht mit klassischen Events nur schwerlich.
von MikeBild, 13.10.2010.

Eigener Kommentar

Sie müssen angemeldet sein, um ein Kommentar zu erstellen.
  • Schwierigkeit: Fortgeschrittene
  • Views: 2653
  • Zur Druckversion
  • Artikel von Rainer Hilmer

Kick it on dotnet-kicks.de

Artikel

Autor

Kick it!

Wenn ihnen dieser Artikel gefällt, bitte "kicken" sie ihn.

WPF Forum | ASP.NET Forum | ASP.NET MVC Forum | Silverlight Forum | Windows Phone 7 Forum | SharePoint Forum | Dotnet Jobs | Dotnet Termine | Developer Blogs | Dotnet News

Das Team | Regeln | Impressum