How do I bind to data from a different thread?

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Jamie Risk

    #1

    How do I bind to data from a different thread?

    I have a form with controls that bind to an object declared in
    the form.

    I have a communications class that is receiver driven. (to get
    data I have to send a command).

    How can I have the receiver thread manipulate the object my form
    controls are bound to?

    Eg.

    namespace myApp
    {
    public partial class myMainForm : Form
    {
    public dataClass myData = new dataClass();

    // Constructor
    public myMainForm()
    {
    InitializeCompo nent();
    this.myDataBind ingSource.DataS ource = this.myData;
    }
    // ...
    private void Read_button_Cli ck(object sender,
    EventArgs e)
    {
    comm.myDevice dev = comm.myDevice() ;
    dev.Open();
    if (dev.IsOpen)
    {
    byte[] packet;
    ... // Create packet
    dev.Transaction (packet);
    }
    }
    }

    namespace comm
    {
    private class responseData
    {
    public bool IsValid { ... }
    }
    public class myDevice : SerialPort
    {
    public void Transaction(byt e[] packet)
    {
    try { base.Write(pack et, 0, packet.Length); }
    catch (TimeoutExcepti on) { return; }

    System.Threadin g.Thread rdThrd;
    rdThrd = new System.Threadin g.Thread(Respon se);
    rdThrd.Start();
    }
    public void Response()
    {
    repsonseData resposne = new responseData();
    while (!response.IsVa lid)
    {
    byte[] data = new byte[512];
    try { base.Read(data, 0,
    Math.Min(this.B ytesToRead,512) ); }
    catch (TimeoutExcepti on) { return; }
    response.scan(d ata);
    }
    // *************** *************** **********
    // bind something akin to this:
    // *************** *************** **********

    myApp.myMainFor m.myData.elemen t1 = response.elemen t1;

    }
    }
    }
    }


  • Dave Sexton

    #2
    Re: How do I bind to data from a different thread?

    Hi Jamie,

    You don't need a reference to MyMainForm, you need a reference to the data
    source object. Try adding a constructor that accepts your DataClass as a
    parameter:

    public class MyDevice : SerialPort
    {
    private readonly DataClass data;

    public MyDevice(DataCl ass data)
    {
    this.data = data;
    }

    public void Response()
    {
    // TODO: set data.element1
    }
    }

    Supply the data source object to MyDevice when it's constructed in MyApp:

    private void Read_button_Cli ck(object sender, EventArgs e)
    {
    Comm.MyDevice dev = new Comm.MyDevice(m yData);

    // TODO: use dev
    }

    An alternative to using a constructor parameter is to accept a DataClass
    parameter in the Transaction method itself. You can then accept DataClass as
    a parameter in the Response method as well and use a ParameterizedTh readStart
    (2.0 framework) by calling the appropriate Thread.Start method overload.

    "ParameterizedT hreadStart Delegate"
    http://msdn2.microsoft.com/en-us/lib...readstart.aspx

    I like the constructor option better because it's type-safe, but it might not
    be appropriate for your architecture.

    BTW, the standard is to use upper camel case for namespace, class, struct and
    enum declarations, just like in the FCL. For example:

    namespace MyApp { public class MyMainForm : Form { ... } }

    --
    Dave Sexton

    "Jamie Risk" <risk.#.@intect us.comwrote in message
    news:OJc9572AHH A.1224@TK2MSFTN GP04.phx.gbl...
    >I have a form with controls that bind to an object declared in the form.
    >
    I have a communications class that is receiver driven. (to get data I have
    to send a command).
    >
    How can I have the receiver thread manipulate the object my form controls
    are bound to?
    >
    Eg.
    >
    namespace myApp
    {
    public partial class myMainForm : Form
    {
    public dataClass myData = new dataClass();
    >
    // Constructor
    public myMainForm()
    {
    InitializeCompo nent();
    this.myDataBind ingSource.DataS ource = this.myData;
    }
    // ...
    private void Read_button_Cli ck(object sender,
    EventArgs e)
    {
    comm.myDevice dev = comm.myDevice() ;
    dev.Open();
    if (dev.IsOpen)
    {
    byte[] packet;
    ... // Create packet
    dev.Transaction (packet);
    }
    }
    }
    >
    namespace comm
    {
    private class responseData
    {
    public bool IsValid { ... }
    }
    public class myDevice : SerialPort
    {
    public void Transaction(byt e[] packet)
    {
    try { base.Write(pack et, 0, packet.Length); }
    catch (TimeoutExcepti on) { return; }
    >
    System.Threadin g.Thread rdThrd;
    rdThrd = new System.Threadin g.Thread(Respon se);
    rdThrd.Start();
    }
    public void Response()
    {
    repsonseData resposne = new responseData();
    while (!response.IsVa lid)
    {
    byte[] data = new byte[512];
    try { base.Read(data, 0,
    Math.Min(this.B ytesToRead,512) ); }
    catch (TimeoutExcepti on) { return; }
    response.scan(d ata);
    }
    // *************** *************** **********
    // bind something akin to this:
    // *************** *************** **********
    >
    myApp.myMainFor m.myData.elemen t1 = response.elemen t1;
    >
    }
    }
    }
    }
    >
    >

    Comment

    • Jamie Risk

      #3
      Re: How do I bind to data from a different thread?

      Thanks, I'll give this a try.

      - Jamie

      Dave Sexton wrote:
      Hi Jamie,
      >
      You don't need a reference to MyMainForm, you need a reference to the data
      source object. Try adding a constructor that accepts your DataClass as a
      parameter:
      >
      public class MyDevice : SerialPort
      {
      private readonly DataClass data;
      >
      public MyDevice(DataCl ass data)
      {
      this.data = data;
      }
      >
      public void Response()
      {
      // TODO: set data.element1
      }
      }
      >
      Supply the data source object to MyDevice when it's constructed in MyApp:
      >
      private void Read_button_Cli ck(object sender, EventArgs e)
      {
      Comm.MyDevice dev = new Comm.MyDevice(m yData);
      >
      // TODO: use dev
      }
      >

      Comment

      • Marc Gravell

        #4
        Re: How do I bind to data from a different thread?

        It isn't clear how your bindings work... however, if they are simple
        Binding() instances, then yup they will break on cross-threaded calls.
        But here is a fix:


        Other than that, you may need to catch events and .[Begin]Invoke()
        manually.

        Marc

        Comment

        • Jamie Risk

          #5
          Re: How do I bind to data from a different thread?

          Marc Gravell wrote:
          It isn't clear how your bindings work... however, if they are simple
          Binding() instances, then yup they will break on cross-threaded calls.
          Truthfully?
          It's not clear to me either ... I'm pretty new at this; as an
          embedded developer it's frightening how much code is
          automagically created with just a few button clicks.
          As an example of my ignorance; in the xxx.Designer.cs source
          file I see things akin to:

          this.myTextData _textBox.DataBi ndings.Add(
          new System.Windows. Forms.Binding(
          "Text",
          this.myDataBind ingSource,
          "myTextData ", true ));

          Do I simply replace the new System....Bindi ng(...) with the
          class you created? And if so, do I put that in the constructor
          for the form?

          - Jamie

          Comment

          • Marc Gravell

            #6
            Re: How do I bind to data from a different thread?

            Yes.

            Basically, you aren't meant to touch the UI from any thread *except* the one
            that created it - this is "thread affinity" and there are lots of reasons.
            In 2.0 it will usually catch you doing this and laugh
            (IllegalOperati onException) in your face.

            Now; I (and others) would argue that in an "observer" or "MVP" scenario, it
            shouldn't matter which thread causes the change to the data object... the UI
            should be able to update itself. Unfortunately the standard MS Binding
            object isn't capable of this, and will ignore any updates that happen on the
            wrong thread.

            Fortunately, the example you have cited:
            DataBindings.Ad d(
            new System.Windows. Forms.Binding(
            can be replaced (as you thought) with a new ThreadedBinding (blah). A word of
            warning though. The designer (in the IDE) is notorious (really, really
            infamous) for botching data bindings and either losing them randomly or
            messing them up. My advice is to use the designer to *create* the bindings,
            but then cut/paste all of the DataBindings code into a private method that
            isn't owned by the designer. This would be a good time to use find/replace
            to change to a ThreadedBinding .
            The downside is that you don't get to see your bindings in the designer. The
            upside is that it doesn't break (losing all your bindings) every 4th day
            (YMMV).

            The other answer here is to do your main work on the background thread, but
            then switch back to the UI thread to do the all important change to the
            objects that triggers the UI update. This is reasonably easy using
            BeginInvoke or Invoke.

            Marc


            Comment

            • Jamie Risk

              #7
              Re: How do I bind to data from a different thread?

              Marc Gravell wrote:
              Fortunately, the example you have cited:
              DataBindings.Ad d(
              new System.Windows. Forms.Binding(
              can be replaced (as you thought) with a new ThreadedBinding (blah). A word of
              warning though. The designer (in the IDE) is notorious (really, really
              infamous) for botching data bindings and either losing them randomly or
              messing them up. My advice is to use the designer to *create* the bindings,
              but then cut/paste all of the DataBindings code into a private method that
              isn't owned by the designer. This would be a good time to use find/replace
              to change to a ThreadedBinding .
              The downside is that you don't get to see your bindings in the designer. The
              upside is that it doesn't break (losing all your bindings) every 4th day
              (YMMV).
              >
              I'd already added the bindings to the form's constructor, and
              everything seemed to work like it used to. However, I'm
              obviously missing something as the background object updating
              isn't causing the UI to update.

              Do I need to add an event handler somewhere?

              - Jamie

              Comment

              • Marc Gravell

                #8
                Re: How do I bind to data from a different thread?

                OK; start simple.

                If you forget the threading (just run the code in question from a
                button-click or something), does it update? Get this working first, *then*
                add threading. I suspect that you aren't providing the necessary hooks for
                data-binding. Data-binding does (as you suggest) rely on eventing.

                For reference, this can be done in several ways:
                Method 1:
                For a property "SomeProper ty", add a matching event "SomePropertyCh anged".
                This is a magic name pair that the runtime looks for when it comes to look
                for a notification sink. So in your case myTextDataChang ed. Note that you
                need to trigger this event when the text changes. Example (I've changed the
                case to C#/CLR standards):

                private string myTextData;
                public string MyTextData {
                get {return myTextData;}
                set {
                if(MyTextData != value) {
                myTextData = value;
                OnMyTextDataCha nged();
                }
                }
                }
                public event EventHandler MyTextDataChang ed;
                protected void OnMyTextDataCha nged() { // just convention
                EventHandler handler = MyTextDataChang ed;
                if(handler!=nul l) handler(this, EventArgs.Empty );
                }

                Method 2:
                As method 1, but you implement INotifyProperty Changed, and include the
                property name in the PropertyChanged EventArgs

                Method 3:
                more involved, but you can write custom component-model AddHandler and
                RemoveHandler methods. I won't detail this unless you are genuinely
                interested.

                Marc


                Comment

                • Jamie Risk

                  #9
                  Re: How do I bind to data from a different thread?

                  Thanks.
                  So.
                  Much.

                  FYI I used Method 1. It was a complicated search and replace
                  for all the properties in question, and seemingly inelegant in
                  all the repetitive code ... but it works. Thanks.

                  Marc Gravell wrote:
                  OK; start simple.
                  >
                  If you forget the threading (just run the code in question from a
                  button-click or something), does it update? Get this working first, *then*
                  add threading. I suspect that you aren't providing the necessary hooks for
                  data-binding. Data-binding does (as you suggest) rely on eventing.
                  >
                  For reference, this can be done in several ways:
                  Method 1:
                  For a property "SomeProper ty", add a matching event "SomePropertyCh anged".
                  This is a magic name pair that the runtime looks for when it comes to look
                  for a notification sink. So in your case myTextDataChang ed. Note that you
                  need to trigger this event when the text changes. Example (I've changed the
                  case to C#/CLR standards):
                  >
                  private string myTextData;
                  public string MyTextData {
                  get {return myTextData;}
                  set {
                  if(MyTextData != value) {
                  myTextData = value;
                  OnMyTextDataCha nged();
                  }
                  }
                  }
                  public event EventHandler MyTextDataChang ed;
                  protected void OnMyTextDataCha nged() { // just convention
                  EventHandler handler = MyTextDataChang ed;
                  if(handler!=nul l) handler(this, EventArgs.Empty );
                  }
                  >
                  Method 2:
                  As method 1, but you implement INotifyProperty Changed, and include the
                  property name in the PropertyChanged EventArgs
                  >
                  Method 3:
                  more involved, but you can write custom component-model AddHandler and
                  RemoveHandler methods. I won't detail this unless you are genuinely
                  interested.
                  >
                  Marc
                  >
                  >

                  Comment

                  • Marc Gravell

                    #10
                    Re: How do I bind to data from a different thread?

                    Welcome.

                    You'll probably want to hit me, but note also that if you have lots of
                    events on an object, you may wish to look at the EventHandlerLis t approach;
                    this reduces the size of each object instance by using a list of the
                    *subscribed* handlers.

                    In my defence, you only mentioned one property, so a single event was the
                    most appropriate solution... and even for a handful I would keep it simple
                    and use simple event backers... but if the count grew I would look at
                    EventHandlerLis t and static reference objects.

                    *But* the standard event approach will work *just fine*. This is just an
                    optimisation mentioned for completeness. It actually makes things slightly
                    more complex, so don't go near it unless you are happy to absorb that cost.

                    Marc


                    Comment

                    Working...