How do I make my UI not Freeze while background co

2019-08-27 06:27发布

So Im trying to make an app that can execute functions send from a client it works fine but the UI freezes while its listening for a message from a Client what do I have to change to make this code run Async? Already tried changing public void ExecuteServer(string pwd) to public async task ExecuteServer(string pwd) but it just tells me that im lacking an await

//Where im calling it
public Form2()
{
    InitializeComponent();
    (ExecuteServer("test"));
}

//The Network Socket im trying to run Async        
public static void ExecuteServer(string pwd)
{
    // Establish the local endpoint  
    // for the socket. Dns.GetHostName 
    // returns the name of the host  
    // running the application. 
    IPHostEntry ipHost = Dns.GetHostEntry(Dns.GetHostName());
    IPAddress ipAddr = ipHost.AddressList[0];
    IPEndPoint localEndPoint = new IPEndPoint(ipAddr, 11111);

    // Creation TCP/IP Socket using  
    // Socket Class Costructor 
    Socket listener = new Socket(ipAddr.AddressFamily,
                SocketType.Stream, ProtocolType.Tcp);

    try
    {
        // Using Bind() method we associate a 
        // network address to the Server Socket 
        // All client that will connect to this  
        // Server Socket must know this network 
        // Address 
        listener.Bind(localEndPoint);

        // Using Listen() method we create  
        // the Client list that will want 
        // to connect to Server 
        listener.Listen(10);
        while (true)
        {
            //Console.WriteLine("Waiting connection ... ");

            // Suspend while waiting for 
            // incoming connection Using  
            // Accept() method the server  
            // will accept connection of client 
            Socket clientSocket = listener.Accept();

            // Data buffer 
            byte[] bytes = new Byte[1024];
            string data = null;

            while (true)
            {
                int numByte = clientSocket.Receive(bytes);

                data += Encoding.ASCII.GetString(bytes,
                                        0, numByte);

                if (data.IndexOf("<EOF>") > -1)
                    break;
            }

            Console.WriteLine("Text received -> {0} ", data);
            if(data == "<EOF> " + "kill")
            {
                Application.Exit();
            } 
            else if (data == "<EOF>" + "getpw")
            {
                sendtoclient(clientSocket, pwd);
            } 
            else
            {
                sendtoclient(clientSocket, "Error 404 message not found!");
            }

            // Close client Socket using the 
            // Close() method. After closing, 
            // we can use the closed Socket  
            // for a new Client Connection 
            clientSocket.Shutdown(SocketShutdown.Both);
            clientSocket.Close();
        }
    }

    catch (Exception e)
    {
        //Console.WriteLine(e.ToString());
    }
}

2条回答
ら.Afraid
2楼-- · 2019-08-27 07:16

Since you're not accessing or changing the UI within the server-loop I would suggest using a Thread.

You could start the new Thread like this:

public Form2()
{
    InitializeComponent();
    Thread serverThread = new Thread(() => ExecuteServer("test"));
    serverThread.Start();
}

A few things to note here though.
First of all, you should never start long running threads inside the constructor. Use the Load event for that. You can create a eventhandler for it if you double click on the form in the designer. You can also do something like this:

public Form2()
{
    InitializeComponent();
    this.Load += (o, e) => StartServer();
}

private void StartServer() 
{
    Thread serverThread = new Thread(() => ExecuteServer("test"));
    serverThread.Start();
}

Next thing to note would be that you currently have no way of stopping the thread other than sending the right data to the socket. You should at least use a volatile bool instead of the true in the outer while loop.

Also you should use Application.Exit as little as possible. With this thread solution, I would suggest just breaking out of the while loop and executing some closing action at the end of the thread-method. Your ExecuteServer-method could look something like this:

public static void ExecuteServer(string pwd, Action closingAction)
{
    // Establish the local endpoint  
    // for the socket. Dns.GetHostName 
    // returns the name of the host  
    // running the application. 
    IPHostEntry ipHost = Dns.GetHostEntry(Dns.GetHostName());
    IPAddress ipAddr = ipHost.AddressList[0];
    IPEndPoint localEndPoint = new IPEndPoint(ipAddr, 11111);

    // Creation TCP/IP Socket using  
    // Socket Class Costructor 
    Socket listener = new Socket(ipAddr.AddressFamily,
                SocketType.Stream, ProtocolType.Tcp);

    try
    {
        // Using Bind() method we associate a 
        // network address to the Server Socket 
        // All client that will connect to this  
        // Server Socket must know this network 
        // Address 
        listener.Bind(localEndPoint);

        // Using Listen() method we create  
        // the Client list that will want 
        // to connect to Server 
        listener.Listen(10);
        while (_shouldContinue)
        {
            //Console.WriteLine("Waiting connection ... ");

            // Suspend while waiting for 
            // incoming connection Using  
            // Accept() method the server  
            // will accept connection of client 
            Socket clientSocket = listener.Accept();

            // Data buffer 
            byte[] bytes = new Byte[1024];
            string data = null;

            while (true)
            {
                int numByte = clientSocket.Receive(bytes);

                data += Encoding.ASCII.GetString(bytes,
                                        0, numByte);

                if (data.IndexOf("<EOF>") > -1)
                    break;
            }

            Console.WriteLine("Text received -> {0} ", data);
            if (data == "<EOF> " + "kill")
            {
                break;
            }
            else if (data == "<EOF>" + "getpw")
            {
                sendtoclient(clientSocket, pwd);
            }
            else
            {
                sendtoclient(clientSocket, "Error 404 message not found!");
            }

            // Close client Socket using the 
            // Close() method. After closing, 
            // we can use the closed Socket  
            // for a new Client Connection 
            clientSocket.Shutdown(SocketShutdown.Both);
            clientSocket.Close();
        }
    }
    catch (Exception e)
    {
        //Console.WriteLine(e.ToString());
    }

    closingAction();
}

And your StartServer would have to be adjusted a bit:

private void StartServer() 
{
    Action closingAction = () => this.Close();
    Thread serverThread = new Thread(() => ExecuteServer("test", closingAction));
    serverThread.Start();
}

This will close the form once the server has ended. Of course you can change that action which is executed.
Also the shouldContinue bool should look something like this: private static volatile bool _shouldContinue = true;

You can of course exchange that for a property or whatever you want just set it to false if you want the loop to end.

Last thing, keep in mind that if you're using blocking calls like listener.Accept(); you will of course not cancel the thread straight away when changing the bool. For these things I would advise you to move away from blocking calls like this and try to find things with timeout for example.

I hope you can start something with this.
Good Luck!

EDIT:
When considering the accepted answer I have to repeat that you should never start long running threads/tasks inside a constructor. If you really want to use async/await instead of tasks please don't do it like the accepted answer suggests.
First of all wrapping the whole method-body in a Task.Run looks horrible and brings a layer of nesting more. There are so many ways you could do this better:

  1. Use a local function and execute Task.Run on that.
  2. Use a separate function and execute Task.Run on that.
  3. If you only want to start it asynchronously once and there are use cases for executing the function synchronously (blocking) then you should keep the function like this and do Task.Run on it when you're calling it.

Also as mentioned in my comment under the accepted answer, it would be much better to use the Load event and do like this in the constructor:
Load += async (o, e) => await Task.Run(() => ExecuteServer("test"));.

Not only fixes this the problem of starting a long running task inside the constructor, but it also makes the call asynchronous right there without any ugly nesting inside the ExecuteServer function (see point 3).
If you want the ExecuteServer function to be asynchronous on its own, see point 1 and 2.

查看更多
Melony?
3楼-- · 2019-08-27 07:17

Use await Task.Run(() => {...}); at the beginning of ExecuteServer and put its code inside {...}.

P.S. Before using the above code, if you're usging any component from UI, insert it's property in a variable. Like this: var name = txtName.Text; and the use the variable.

查看更多
登录 后发表回答