0

I have a little problem, trying to code device to device porgram using JSON.

My device to device is working well, I'm using it to send JSON to an other device throug a homemade D2D server. So one of my client is sending request to an other client named "server", that server is doing some stuff ( doing some request on an Asterisk server ), everything is ok with that.

But i'm facing a problem : When my client is sending request to my server, i'm waiting for the answer from the server with a waitOne on a autoResetEvent. Depending of the request, my form will show a pop-up. But my form is very slow to show popup, I think it's due to those waitOne, i can send like 10 request in less than 3 seconds, so i think my form is slow down cause of these.

Basically, I'm using a thread that's reading data on a socket, when my data is complete ( the entire Json is retrieve ), i'm sending an event, then I have to release the reading thread, so i'm doing a "BeginInvoke" so my form will handle the event :

 _sync.BeginInvoke(ChannelChange, new Object[] {this, ec});

After showing the popup, my form is handling multiple other events, it's doing some request to the server ( so the thread is stopped during the multiple request ).

SO I guess, it's why my pop up is showing very slowly. So my question is how can I avoid this slow down ?

A little resume :

MyForm is using a Client, that client contains some method , like "retrieveChannelById". When my reading thread is retrieving a complete Json, it's throwing an event invoking my form ( If I doesn't invoke the event with my form, my reading thread is gonna be stopped and it's worst ) My form is performing some Client method such as "retrieveChannelById" setting the thread to pause for a while ( usually less than 1 second, but it's doing a lot of request pretty fast ).

So I would like to know, how can I can specify a thread to execute those method without pausing the form thread ?

But still, when i'm handling an event, i'm doing

Channel chan = Client.GetChannelByChanID(e.ChannelId);

I really don't know how to handle this slowing down of the form, Do I have to execute those method in an other thread ? ( It won't change anything I guess, since my form has to wait for the other thread to get the channel right ? ). I need a solution so my form thread is'nt set on WaitOne, I think it's my issue.

It's really hard to explain, actually ...

here is my code for the method retrieveChannelById :

   SendD2DMessage("*", "server", "{\"classname\":\"GetChannelByChanID\",\"chanID\":\"" + chanID + "\"}");
        waitingData = "reponseGetChannelByID";           
        if (this._messageRecieved.WaitOne(5000))
        {

            try
            { 
                if (dataRecieved == "null")
                {
                    Console.WriteLine("But data null");
                    return null;
                }
                else
                {
                    Channel chan = JsonConvert.DeserializeObject<Channel>(dataRecieved);
                    Console.WriteLine("Channel received : " + chan.Extension);
                    return chan;
                }
            }
            catch (Exception ex)
            {
                SynsipClientLog.Log("Error while deserializing Channel Object in GetChannelByID Method :" + ex.Message);
                return null;
            }
        }
        else
        {
            waitingData = "";
            Console.WriteLine("Channel NOT RECEIVED");
            SynsipClientLog.Log("No response from D2DConsole in GetChannelByChanId method");
            return null;
        }

Here is the ChannelChange code, this code is in my main form.

  void SynsipCli_ChannelChange(object sender, SynsipRemoteChannelChangeEventsArgs e)
    {
        try
        {
            //Console.WriteLine("test ChannelChange");
            Channel chan = SynsipCli.GetChannelByChanID(e.ChannelId);
            Channel linkedChan = null;
            if (chan != null && chan.LinkedChannelID.Length>1)
            {
                linkedChan = SynsipCli.GetChannelByChanID(chan.LinkedChannelID);
            }

            // alertes pour les trunck
            if (e.Channelcategory == ChannelCategory.Trunck && !MenuDoNotDisplay.Checked)
            {
                bool isGeneralNumber = false;

                if (chan != null)
                {
                    foreach (string str in SynsipCli.AstParameters.GeneralNumbers)
                    {
                        if (chan.CallerIDNum.Contains(str) || chan.LinkedCallerID.Contains(str) || chan.DNIS.Contains(str) || chan.Extension.Contains(str))
                            isGeneralNumber = true;
                    }

                    if (chan.State != ChannelState.Down && chan.State != ChannelState.Hangup && chan.State != ChannelState.Unknow && (chan.LinkedCallerID == Extension || chan.CallerIDNum == Extension || isGeneralNumber))
                    {

                        LaunchAlert(chan);
                    }

                }
            }
            else if (linkedChan != null && linkedChan.ChannelCategory == ChannelCategory.Trunck)
            {
                bool isGeneralNumber = false;

                if (linkedChan != null)
                {
                    foreach (string str in SynsipCli.AstParameters.GeneralNumbers)
                    {
                        if (linkedChan.CallerIDNum.Contains(str) || linkedChan.LinkedCallerID.Contains(str) || linkedChan.DNIS.Contains(str))
                            isGeneralNumber = true;
                    }

                    if (linkedChan.State != ChannelState.Down && linkedChan.State != ChannelState.Hangup && linkedChan.State != ChannelState.Unknow && (linkedChan.LinkedCallerID == Extension || linkedChan.CallerIDNum == Extension || isGeneralNumber))
                    {
                        LaunchAlert(linkedChan);

                        //LaunchAlert(tchan);

                    }

                }

            }
            if (((chan != null && (chan.CallerIDNum == txtExtention.Text || chan.LinkedCallerID == txtExtention.Text)) || e.Extension == txtExtention.Text) && !MenuDoNotDisplay.Checked) // alertes pour le local
            {
                string ext = SynsipCommunicator.Properties.Settings.Default.Extention;

                Extension exten = SynsipCli.GetExtension(ext);

                foreach (Channel tchan in exten.Channels)
                {
                    if (tchan != null && tchan.CalllType == CallType.Internal)
                    {
                        LaunchAlert(tchan);


                    }
                }
            }

            // suppression des alerts qui ne doivent plus exister
            for (int i = 0; i < Alerts.Count; i++)
            {
                Channel tchan = SynsipCli.GetChannelByChanID(Alerts[i].chanId);
                if (tchan == null)
                    Alerts[i].Close();
                else if (tchan.State == ChannelState.Down || tchan.State == ChannelState.Hangup || tchan.State == ChannelState.Unknow)
                    Alerts[i].Close();
            }
        }
        catch (Exception exe)
        {
            //Console.WriteLine("ERREUR CHANNEL CHANGE " + exe.Message);
        }

    }

The LaunchAlert method is just checking some stuff in my Channel object, create a frame and then show the frame :

 if (!MenuDoNotDisplay.Checked)
            {
                ShowWindow(frma.Handle, 4); //Pour éviter de prendre le focus
                SetWindowPos(frma.Handle, -1, frma.Location.X, frma.Location.Y, frma.Width, frma.Height, 0x0010);
            }
  • Maybe I can check the state of my form before invoking the event ? – Pyr0technicien Apr 18 '14 at 09:58
  • The WaitOne is called inside the Form's thread? That would stop the UI thread from responding. Is it first called inside the constructor or onload? If so, best approach would indeed be using a seperate thread (an async-await construction if using framework 4.5), but also waiting with calling the method inside onshown would enable the user to see the form before the waiting starts. – Me.Name Apr 18 '14 at 10:31
  • Actually, my form is calling a method from a different class : Like this in my form : `code` – Pyr0technicien Apr 22 '14 at 09:11
  • Actually, my form is calling a method from a different class : Like this in my form : ` Channel chan = Client.MyMethodWithWaitOne() ` so my UI is waiting to receive a channel, and the UI thread is performing this method, then it's using that channel to show my form. But after that I may receive an other event that will also use this method. I think my form has not enough time to show before a new event is triggered. So I need something to wait for the form to end loading before triggering my event... – Pyr0technicien Apr 22 '14 at 09:18
  • And at which point is the Client.MyMethodWithWaitOne called? If inside the constructor or onload, the ui is not showing the form yet. As a starting point, Can you override onshown inside the form and put the call there, to see what happens? (separate thread would be better, but if onshown works, at least the culprit is identified) – Me.Name Apr 22 '14 at 09:30
  • Actually, when I receive a event, I'm creating a pop-up, and I fill it with the data received in "Client.MyMethodWithWaitOne", when My pop-up has all the data, I make the pop-up visible. But I Instantly receive an other event of the same type, wich make me call the MyMethodWithWaitOne again, blocking my thread, I guess my thread is going to pause before it has finished to show my frame right ? The async / await seems to be a good solution, but i have to study it for a while, I don't really get how it works. – Pyr0technicien Apr 22 '14 at 14:00
  • And for exemple, I also have a timerElapsed event, wich update my pop-up every second, showing a timer in the popup. but if an event, that need to retrieve a data from a method with a waitone, is triggered, my timerElapsed will not update untill I receive the response from my sever. So I need to transform my WaitOne to an async wait or something like that I guess ... – Pyr0technicien Apr 22 '14 at 14:50
  • Could you add the code of `ChannelChange` ? – Me.Name Apr 23 '14 at 08:28
  • Here you go, I did'nt add the whole code of LaunchAlert but it does'nt really matter for now. – Pyr0technicien Apr 23 '14 at 08:47
  • If I create a new thread to launch the ChannelChange event ( without invoking my UI ) every time i've receive a data and invoking the UI on the LaunchAlert method, my UI is way more responsive but I think there is a problem with my Alert table... Even If i receive a null channel, my Alert[i] will never close. – Pyr0technicien Apr 23 '14 at 08:57
  • Funny, was just going to suggest exactly that. Only invoking LaunchAlert since that seems to be the only bit that needs the UI thread :) But on to the next part then. The Close() on alert is never called, or the record is not removed from the alerts table? The code doesn't show anything where the instance would be removed from the table/list after close is called (unless that is handled inside the Close method)? – Me.Name Apr 23 '14 at 09:16
  • Do you think that create a new Thread per event is really a good idea ? Actually, i when I trough my receiveData event, and then my ChannelChange event, my reading socket thread will execute the ChannelChange method, so it will block, and I just can't block my reading thread or my JSON will be split. Maybe is there an other way than creating a thread for each event ? No the instance is never removed, actually, when the frame is created, it's added in the Alert table. On every ChannelChange, at the end, the is a loop checking the state of every channel in the table. I can see using the debugge – Pyr0technicien Apr 23 '14 at 09:28
  • that the thread ( so not the UI thread ) is executing the Alert[i].close, but the frame doesn't close, I guess it is my UI to do that job, not the thread created to execute the ChannelChange method. – Pyr0technicien Apr 23 '14 at 09:29
  • Manually creating a new thread per event is indeed not a good idea. Using Tasks however is. Those only start a new thread if needed. Take a look at Task.Run http://msdn.microsoft.com/en-us/library/vstudio/hh195051 and the TPL library http://msdn.microsoft.com/en-us/library/dd537609%28v=vs.110%29.aspx – Me.Name Apr 23 '14 at 09:42
  • you mean the close worked before? Don't know if the UI thread is needed, but then it's easy enough to put the close checking in a separate method and invoking that. – Me.Name Apr 23 '14 at 09:44
  • I'll have a look at those Task, I read a bit about that yesterday trying to make it work with an async method. Thanks for your precious help anyway, i was so lost in my code :) – Pyr0technicien Apr 23 '14 at 10:16

1 Answers1

0

Don't do any blocking waits (like the event.WaitOne()) on the UI thread. This will lead exactly to to the dead behavior you are describing, because the UI can't update itself during this time period.

Instead you should work asynchronously: Only start the operation on UI thread and then immediatly return. When the operation finishes then change back to the UI thread (e.g. with BeginInvoke), evaluate the result and whether your operation is still necessary and update the UI according to the result of the operation.

If you want a timeout like your 5s then start the send operation asynchronously and start a timer in the UI asynchronously in parallel. Both should finish back in the UI thread. Then see whether your timer finishes first or you first receive the result of the operation and handle that situation.

You might also be able to wrap your Send operation in a C# TPL Task object or use the C# async/await features to make the whole code even a bit more readable and composable.

Matthias247
  • 9,836
  • 1
  • 20
  • 29
  • Actually I wanted to try this solution, but i'm really lost in those Asynchronous code ... When i'm in my request channel method, how can I "wait" for the answer ( actually for the event ReceivedData ) without blocking the thread ? When I was holding my thread, all the events I was receiving ( such as ChannelChange ) were stacked and throw after my thread completed. I also need to stay in my method "GetChannelByID" while i'm waiting for the answer. When I launch getChannelByID from my UI async, does it gonna wait to get the channel or will it go further in the code ? Cause the line after is – Pyr0technicien Apr 23 '14 at 10:13
  • using the channel received, so it has to wait for the channel anyway ... Maybe I don't get how async is working. – Pyr0technicien Apr 23 '14 at 10:14
  • Actually, how can I send a request in the method, wait without blocking for the ReceiveData event, check the data, if it is a response, stop the waiting process and keep going in my method ? I can't find any solution without a autoresetEvent :( – Pyr0technicien Apr 23 '14 at 10:37
  • You can't wait in the method without blocking the UI. These things are contradictionary. The only thing you can to is do the send in the method, return from the method after you receive a response in your background thread start a new method in the UI thread which processes the response and updates the UI again. – Matthias247 Apr 23 '14 at 11:07
  • I can't really modify the UI, i'm upgrading an existing program and they asked me to mostly modify the .net remoting into a JSON system, trying to keep the signature of the methods as much as I can. Your solution would be fine and I tought about it but I'll ave to totally modify my UI wich is not a possibility :( – Pyr0technicien Apr 23 '14 at 11:59