如何在后台代码运行C#时使UI不冻结

时间:2019-06-17 15:24:33

标签: c# asynchronous

因此,我试图制作一个可以执行从客户端发送的功能的应用程序,它工作正常,但UI冻结,同时侦听来自客户端的消息时,我需要更改些什么才能使此代码运行Async?已经尝试将public void ExecuteServer(string pwd)更改为公共异步任务ExecuteServer(string pwd),但这只是告诉我即时通讯没有等待状态

//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 个答案:

答案 0 :(得分:2)

由于您不在服务器循环中访问或更改UI,因此建议您使用线程。

您可以像这样启动新线程:

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

这里要注意几件事。
首先,永远不要在构造函数内部启动长时间运行的线程。为此,请使用Load事件。如果双击设计器中的表单,则可以为其创建事件处理程序。您还可以执行以下操作:

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

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

接下来要注意的是,除了将正确的数据发送到套接字外,您目前无法停止线程。您至少应在外部while循环中使用volatile bool而不是true

此外,您应尽量少使用Application.Exit。使用这种线程解决方案,我建议您只是打破while循环,并在线程方法的末尾执行一些关闭操作。您的ExecuteServer方法可能看起来像这样:

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();
}

您的StartServer必须进行一些调整:

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

服务器结束后,这将关闭表单。当然,您可以更改执行的动作。
shouldContinue布尔也应如下所示: private static volatile bool _shouldContinue = true;

您当然可以将其交换为属性,或者如果希望循环结束,只需将其设置为false即可。

最后,请记住,如果使用listener.Accept();之类的阻塞调用,则在更改布尔值时当然不会立即取消线程。对于这些事情,我建议您远离阻塞这样的呼叫,并尝试例如在超时的情况下进行查找。

希望您可以以此为起点。
祝好运!

编辑:
在考虑接受的答案时,我必须重复您永远不要在构造函数内部启动长时间运行的线程/任务。如果您确实要使用async / await代替任务,请不要像接受的答案所建议的那样做。
首先,将整个方法主体包装在Task.Run中看起来很可怕,并带来了更多的嵌套层。有很多方法可以更好地做到这一点:

  1. 使用local function并在其上执行Task.Run
  2. 使用单独的函数并在其上执行Task.Run
  3. 如果您只想异步启动一次,并且有一些同步执行(阻塞)功能的用例,那么您应该保留这样的功能,并在调用它时对其进行Task.Run

另外,正如我在接受的答案下的评论中所提到的,使用Load事件并在构造函数中执行以下操作会更好:
Load += async (o, e) => await Task.Run(() => ExecuteServer("test"));

不仅解决了在构造函数内部启动长时间运行的任务的问题,而且还使调用在那里异步进行,而没有ExecuteServer函数内部的任何丑陋嵌套(请参见第3点)。
如果您希望ExecuteServer函数本身是异步的,请参见第1点和第2点。

答案 1 :(得分:0)

在ExecuteServer的开头使用await Task.Run(() => {...});,并将其代码放入{...}中。

P.S。在使用上面的代码之前,如果您要使用UI中的任何组件,请将其属性插入变量中。像这样:var name = txtName.Text;并使用变量。