How to use"DA.SetData(....)"from another thread?

I am doing a component for TCP server,to listen “string” from a client.
this is the sample code:

    protected override void SolveInstance(IGH_DataAccess DA)
    {
        int port = 0;
        DA.GetData("Port", ref port);
        thrListener = new Thread(new ThreadStart(Listen));
        thrListener.Start();
        DA.SetData("Output", outputStr);
     }
      private void Listen()
    {
         Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        listener.Bind(new IPEndPoint(IPAddress.Any, 59152));
        while (true)
        {
            listener.Listen(1);
            Socket socket = listener.Accept();
            NetworkStream ntwStream = new NetworkStream(socket);
            StreamReader strmReader = new StreamReader(ntwStream);
            outputStr = strmReader.ReadToEnd();

            Message = outputStr;
            Grasshopper.Instances.RedrawCanvas();
            
            socket.Close();
        }
    }

when i run the code,the “Message” can show realtime,but the DA’s output is empty…i try a lot of way,such as add use a global variable gDA to reference DA,and add “gDA.setdata(…)” in the While(true) {…},but it can not work…
so may be someone can have a way to invoke DA.setData in a workerThread.Thank you.

You should assign the value that comes out of the thread to a class level variable (not a global/static). This assignment will be atomic if the data is a reference type or a value type of 64 bits or less. You then trigger ScheduleSolution() on the document which contains your component. Register a callback with that schedule. In the callback expire your component. In the subsequent call to SolveInstance() assign your class level variable to your outputs.

You need all these steps because your process runs on a thread other than the UI thread.

2 Likes

David @DavidRutten ,for example ,if run ScheduleSolution(10,callback),it means begin to run SolveInstance() at once,and 10ms later,“callback” is begin to run?
Thank you

GH_Document.ScheduleSolution(10, callback) is technically a request, not an instruction as such.

During a solution various objects may all request future solutions to be triggered after some delay. Whenever a new request comes in it is either ignored if another, earlier request already exists, or it overwrites the existing schedule with the earlier one.

Furthermore it depends on when the method is called. If it happens during a solution then the request is simply remembered and a timer for 10 milliseconds won’t be started until after the solution completes. If however you call this method while there is no solution running, a timer is started immediately.

The best way to think of a schedule is “a new solution will start after the current one finishes not much later than my requested delay”. It could be much earlier if someone else schedules a solution with a shorter delay, or even if the user does something which triggers a solution.

At any rate, if you schedule a solution and you provide a callback method, that callback will be called whenever the next solutions starts, no matter whether that’s much earlier than you requested. It will not be called again after that.

thank you David,and can i think this way? : when GH_Document.ScheduleSolution(10, callback) run,no run the SolveInstance() again…the only thing “GH_Document.ScheduleSolution(10, callback)” do is: run callback in the UI thread after some delay?

No it will run a new solution. The callback is there so you can prepare for this solution, for example by expiring the objects which you want to solve.

David,you tell me it will run a solution,but i find it will not,for example:

    protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
    {
    }
    protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
    {
    }
    private string s = null;
    protected override void SolveInstance(IGH_DataAccess DA)
    {
        if (s == null)
        {
            Thread t = new Thread(NewThreadMethod);
            t.IsBackground = true;
            t.Start();
        }
        else
        {
            s += "M,";
            Message = s;
        }
    }
    private void NewThreadMethod()
    {
        for (int i = 0; i < 10; i++)
        {
            Thread.Sleep(1000);
            var calllater = new GH_Document.GH_ScheduleDelegate(UpdateSetData);
            Grasshopper.Instances.ActiveCanvas.Document.ScheduleSolution(1, calllater);
        }
    }
    private void UpdateSetData(GH_Document gh)
    {
       // ExpireSolution(false);
        s += "T,";
        Message = s;
    }

when i run component,the Message shows:
image
it no run a new solution.it is not agree with you told me,David… @DavidRutten ,can you explain it?

later… if i uncomment ExpireSolution(false) in the UpdateSetData(),the component shows:
image
this time,the component run new solution.

and i am confuse about the bool value in expiresolution(bool) when bool is true and false,what it mean…because the api document say little about that.Thank you for explain that David.

Your component runs once and is then in the Solved state. Since it has no inputs, the only way to expire it is to specifically call ExpireSolution(false) on it. You commented out that code, so your component never expires, so nothing ever happens because a solution only runs if there is something to be done.

If you expire your object from the schedule callback, then the scheduled solution will actually run.

Yep, that is confusing. The boolean argument tells the component to trigger a solution once it’s done expiring. You want to set this to TRUE if you are making a single change via the UI. For example if your component expires because the user clicked on a menu item, you need to (a) deal with the menu click, (b) expire the relevant component, (c) start a new solution so the new state is computed.

If however you are expiring lots of objects all in one go, then you want to supply FALSE. For example Galapagos may change any number of sliders and only then compute the new state of the graph. So it calls ExpireSolution(false) on all sliders and when it’s done making all the changes, only then does it start a new solution by calling on the GH_Document directly.

From within a schedule callback you also always use FALSE, since a new solution is happening anyway, it has been scheduled after all. You don’t have to start it yourself.

2 Likes

thank you David!

David,i have a question: how to only update output data?

in this gif, you can see,the component can update its self (every 1000ms add 10),and show the result in Message ,but it cannot update the output( the only way to update is to mouse drag a wire to connect to panel)
this is code:

 protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
    {
    }
    protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
    {
        pManager.AddNumberParameter("Number", "N", "", GH_ParamAccess.item);
    }
    private double s = 0;
    System.Threading.Timer timer;
    IGH_DataAccess da;
    protected override void SolveInstance(IGH_DataAccess DA)
    {
        da = DA;
       if(timer==null)
        {
            TimerCallback tcb = new TimerCallback(add);
            timer = new System.Threading.Timer(tcb, null, 0, 1000);
        }
        Message = s.ToString();
        DA.SetData("Number", s);
    }
    private void add(object o)
    {
        s = s + 10;
        GH_Document doc = this.OnPingDocument();
        try
        {
            if (doc == null)
            {
                return;
            }
            doc.ScheduleSolution(1, UpdateSetData);
        }
        catch(Exception)
        {
        }
    }
    private void UpdateSetData(GH_Document gh)
    {
        Message = s.ToString();
        da.SetData("Number", s);
    }

@DavidRutten,you can find there has not “ExpiredSolution(false)” in “UpdateSetData”…so the solution will not expire and will not run.(because i just want to update the output data,neednot to run solution again)…i know that,i can update the output data by adding expiredsolution in “UpdateSetData”,but,just for curiosity, can i donnot use expiredsolution(like this code),and also can make a output data update?Thank you!