【问题标题】:How do I make my UI not Freeze while background code is running C#如何在后台代码运行 C# 时使我的 UI 不冻结
【发布时间】:2019-10-31 05:28:25
【问题描述】:

所以我试图制作一个可以执行从客户端发送的函数的应用程序,它工作正常,但 UI 在侦听来自客户端的消息时冻结,我必须更改什么才能使此代码异步运行?已经尝试将 public void ExecuteServer(string pwd) 更改为 public async task 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());
    }
}

【问题讨论】:

  • 你试过Task.Run(() => ExecuteServer("test"));没有任何其他异步语法?
  • 虽然其他答案在使用 await Task.Run(() => {...}); 时是正确的;这背后的原因是因为UI是在单线程上运行的,如果你想运行一些像更新进度条这样的代码,它会锁定UI的线程并专注于进度条,或者其他与UI无关的后台代码就像您的网络代码一样。使用 await Task.Run(() => {...});将另一个线程专用于一些后台工作。希望这会有所帮助。
  • 由于您在服务器循环期间没有做任何与 UI 相关的操作,我建议您使用老式线程而不是 Task.Run。你可以找到很多关于如何做到这一点的教程和资源。其中许多甚至使用套接字。

标签: c# asynchronous


【解决方案1】:

由于您没有在服务器循环中访问或更改 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 bool 也应该是这样的: 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) =&gt; await Task.Run(() =&gt; ExecuteServer("test"));

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

【讨论】:

  • 不客气。还有投反对票的人,你能解释一下原因吗?
【解决方案2】:

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

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

【讨论】:

  • 如果你真的想使用 async/await 而不是线程,最好使用 Load 事件并在构造函数中这样做:Load += async (o, e) =&gt; await Task.Run(() =&gt; ExecuteServer("test"));
  • 有不明白或想了解更多的请参考my edited answer
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-03
  • 1970-01-01
  • 2011-09-26
相关资源
最近更新 更多