Home > Software engineering >  Async function freezes UI thread
Async function freezes UI thread

Time:11-15

I have an async function which still freezes / lags the UI thread for me when I execute it. This is my function calling it.

private void TcpListenerLogic(object sender, string e)
        {
            Application.Current.Dispatcher.BeginInvoke((Action)async delegate {
                try
                {
                    dynamic results = JsonConvert.DeserializeObject<dynamic>(e);
                    if (results.test_id != null)
                    {
                        // Get properties for new anchor
                        string testInformation = await CommunicationCommands.getJsonFromURL(
                            "http://"   ServerIP   ":"   ServerPort   "/api/"   results.test_id);
                    }                    
                }
                catch (Exception exception)
                {
                    // Writing some Trace.WriteLine()'s
                }
            });
           
        }

And this is the async function that freezes my UI Thread

        public static async Task<string> getJsonFromURL(string url)
        {
            
            try
            {
                string returnString = null;
                using (System.Net.WebClient client = new System.Net.WebClient())
                {
                    returnString = await client.DownloadStringTaskAsync(url);
                }
                return returnString;
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
                return null;
            }

        }

I already tried to make everything in TcpListenerLogic run in a new Thread:

            new Thread(() =>
            {
                Thread.CurrentThread.IsBackground = true;

            }).Start();

Which resulted in the whole UI completely freezing. And I tried to make TcpListenerLogic async and await the dispatcher, which also made everything freeze permanently. I also tried to make TcpListenerLogic async and leave the dispatcher. The dispatcher is only there because I normally have some UI code in there, which I left out for my tests.

I have ventured far through the internet, but no BackgroundWorker, ThreadPool or other methods helped me in my endeavour. If anyone has help for this particular problem, or a resource that would improve my understanding of async functions in C#, I would much appreciate it.

Edit

As requested a deeper insight in how this event handler is called. I have System.Net.Websocket, which is connected to the Backend API I am working with and triggers an event, everytime he receives new Data. To guarantee the socket listens as longs as it is open, there is a while loop which checks for the client state:

        public event EventHandler<string> TcpReceived;
        public async void StartListener(string ip, int port, string path)
        {
            try
            {
                using (client = new ClientWebSocket())
                {
                    try
                    {   // Connect to backend
                        Uri serverUri = new Uri("ws://"   ip   ":"   port.ToString()   path );
                        await client.ConnectAsync(serverUri, CancellationToken.None);
                    }
                    catch (Exception ex)
                    {
                        BackendSettings.IsConnected = false;
                        Debug.WriteLine("Error connecting TCP Socket: "   ex.ToString());
                    }
                    state = client.State;
                    // Grab packages send in backend
                    while (client.State == WebSocketState.Open || client.State == WebSocketState.CloseSent)
                    {
                        try
                        {
                            // **Just formatting the received data until here and writing it into the "message" variable**//
                            TcpReceived(this, message);

                            // Close connection on command
                            if (result.MessageType == WebSocketMessageType.Close)
                            {
                                Debug.WriteLine("Closing TCP Socket.");
                                shouldstayclosed = true;
                                await client.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
                                break;
                            }
                            state = client.State;
                        }
                        catch
                        {
                            BackendSettings.IsConnected = false;
                            state = client.State;
                        }
                    }
                    state = client.State;
                }
            }
            catch (Exception ex)
            {
                // Some error messages and settings handling
            }
        }

The Event has a handler attached:

TcpReceived  = TcpListener_TcpReceived;

And this is the Handler, which calls the previously seen "TcpListenereLogic".

        private void TcpListener_TcpReceived(object sender, string e)
        {
            TcpListenerLogic(sender, e);

            //App.Current.Dispatcher.BeginInvoke(new Action(() => {
            //      TcpListenerLogic(sender, e);
            //}));

            //new Thread(() =>
            //{
            //    Thread.CurrentThread.IsBackground = true;
            //    TcpListenerLogic(sender, e);
            //}).Start();
        }

I previously had the "TcpListenereLogic" as the handler, but I wanted to try different methods to call it. I also left in the commented out part, to show how the call of "TcpListenereLogic" looked already. All my attempts were with all mentioned setups and sadly lead to nothing.

CodePudding user response:

Thank you very much @TheodorZoulias for helping me to find the solution to my problem.

It turns out it wasn't the async function itself, but rather how often it gets called. It got called roughly ~120 times every second. My solution starts by calling the Listener method over a new Thread:

new Thread(() =>
{
    Thread.CurrentThread.IsBackground = true;
    MainWindow.tcpListener.StartListener(ip, portNumber, "/api/");
}).Start();

To limit the amount of calls that happen every second I added a dispatcher timer, that resets a bool after it has been used for a call, by my Event.

readonly System.Windows.Threading.DispatcherTimer packageIntervallTimer = 
                            new System.Windows.Threading.DispatcherTimer();
        bool readyForNewPackage = true;
        private void ReadyForPackage(object sender, EventArgs e)
        {
            readyForNewPackage = true;
        }

        public async void StartListener(string ip, int port, string path)
        {
            packageIntervallTimer.Interval = TimeSpan.FromMilliseconds(50);
            packageIntervallTimer.Tick  = (s, e) => { Task.Run(() => ReadyForPackage(s, e)); };
            packageIntervallTimer.Start();

Then I wrapped everything inside the while loop into an if condition based on the bool, the most important part was to have my "event EventHandler TcpReceived" in there:

// Grab packages sent in backend
while (client.State == WebSocketState.Open || client.State == WebSocketState.CloseSent)
{
    if (readyForNewPackage == true)
    {
        readyForNewPackage = false;
        try
        {
           ....
           TcpReceived(this, message);
           ....
        }
        catch
        {
            ...
        }
    }
}

I added my TcpListenerLogic to the Eventhandler:

TcpReceived  = TcpListenerLogic;

And my TcpListenerLogic now looked like this (names have been changed):

private async void TcpListenerLogic(object sender, string e)
{
    try
    {
        dynamic results = JsonConvert.DeserializeObject<dynamic>(e);
        if (results.test_id != null)
        {
            string testID = "";
            if (results.test_id is JValue jValueTestId)
            {
                testID = jValueTestId.Value.ToString();
            }
            else if (results.test_id is string)
            {
                testID = results.test_id;
            }

            // Get properties for new object
            string information = await CommunicationCommands.getJsonFromURL(
                "http://"   ServerIP   ":"   ServerPort   "/api/"   testID );
            if (information != null)
            {
                await App.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    // Create object out of the json string
                    TestStatus testStatus = new TestStatus();
                    testStatus.Deserialize(information);
                    if (CommunicationCommands.isNameAlreadyInCollection(testStatus.name) == false)
                    {
                        // Add new object to the list
                        CommunicationCommands.allFoundTests.Add(testStatus);
                    }
                }));
            {
    }
    catch (Exception exception)
    {
        ....
    }

}

Adding a new Thread to execute any step results in problems, so keep in mind that all this uses the thread created at the beginning for "StartListener"

  • Related