如何阻止我的UI冻结?

时间:2018-04-16 14:03:20

标签: c# winforms user-interface

我为我的大学学位创建了一个远程管理工具。我目前真的陷入了我的代码中的一个错误,并想知道是否有人可以解释我的问题。

我有应用程序,服务器和客户端。服务器运行正常。然而,客户端是冻结的应用程序。

在连接到服务器之前,客户端工作正常。连接到服务器后,客户端将一直冻结在屏幕上。

我已经将错误缩小到特定的代码段,而运行应用程序的代码并没有冻结。但是,该应用程序也不起作用。

以下是该代码的示例:

private static void ReceiveResponse()
    {

        var buffer = new byte[2048]; //The receive buffer

        try
        {
            int received = 0;
            if (!IsLinuxServer) received = _clientSocket.Receive(buffer, SocketFlags.None); //Receive data from the server
            else received = _sslClient.Read(buffer, 0, 2048);
            if (received == 0) return; //If failed to received data return
            var data = new byte[received]; //Create a new buffer with the exact data size
            Array.Copy(buffer, data, received); //Copy from the receive to the exact size buffer

            if (isFileDownload) //File download is in progress
            {
                Buffer.BlockCopy(data, 0, recvFile, writeSize, data.Length); //Copy the file data to memory

                writeSize += data.Length; //Increment the received file size

                if (writeSize == fup_size) //prev. recvFile.Length == fup_size
                {


                    using (FileStream fs = File.Create(fup_location))
                    {
                        Byte[] info = recvFile;
                        // Add some information to the file.
                        fs.Write(info, 0, info.Length);
                    }

                    Array.Clear(recvFile, 0, recvFile.Length);
                    SendCommand("frecv");
                    writeSize = 0;
                    isFileDownload = false;
                    return;
                }
            }

            if (!isFileDownload) //Not downloading files
            {
                string text = (!IsLinuxServer) ? Encoding.Unicode.GetString(data) : Encoding.UTF8.GetString(data); //Convert the data to unicode string
                string[] commands = GetCommands(text); //Get command of the message



                foreach (string cmd in commands) //Loop through the commands
                {
                    HandleCommand(Decrypt(cmd)); //Decrypt and execute the command
                }
            }
        }
        catch (Exception ex) //Somethind went wrong
        {
            MessageBox.Show(ex.Message);
            RDesktop.isShutdown = true; //Stop streaming remote desktop
           // MessageBox.Show("Connection ended");
        }
    }

此代码是用户从服务器接收请求的方式。所以它是一个每100ms运行一次的计时器。

我想知道计时器是否与它有关。在它处于While(真)循环之前,我遇到了同样的问题,这让我觉得它是实际的代码,使得UI冻结。

即使应用程序被冻结,代码仍然有效,除了UI之外,应用程序上的所有内容都可以正常工作。

这真的令人沮丧,而我无法确定代码会导致应用程序冻结。

非常感谢任何帮助。

谢谢提前。

6 个答案:

答案 0 :(得分:3)

到目前为止有六个答案,没有任何有用,正确或可操作的建议。忽略它们。

  

我已经将错误缩小到特定的代码段,而运行应用程序的代码没有冻结

好,这是诊断问题的第一步。 更进一步。这里究竟哪些方法具有高延迟?我的猜测是ReceiveRead,但有几个可能的候选人,可能不止一个。

一旦确定了每种高延迟方法,确定它是否很慢,因为它正在使用CPU或等待IO。

如果它因使用CPU而变慢,那么你想要做的是将该操作移到另一个CPU并等待结果。您可以像其他答案一样建议:使用Task.Run将工作移到后台线程,然后await结果。

如果它因等待IO而变慢,那么不会将操作移动到后台线程,除非您没有其他选择。后台工作程序API旨在将工作卸载到其他CPU,而IO不受CPU限制。您要在此处执行的操作是使用异步IO操作。如果阻止者为Receive,那么请使用ReceiveAsync代替await生成Task

你还应该使你的方法async并返回一个Task,然后由其调用者等待,等等,直到事件处理程序完成所有事情。

继续执行此过程,直到您确定方法中的每个操作超过30毫秒,并使其在工作线程上异步,如果CPU绑定,或在IO绑定时使用异步IO方法。那么你的UI应该永远不会有超过30毫秒的延迟。

说到事件处理程序......

  

此代码是用户从服务器接收请求的方式。所以它是一个每100ms运行一次的计时器。

听起来非常非常错误。首先,"接收来自服务器的请求"?服务器代表客户端发出请求,反之亦然。

其次,这听起来像是处理问题的错误方法。

如果您正确地异步验证此代码,则不需要定时器来不断轮询套接字。您应该只是异步读取,并且读取成功并回叫您,或者它超时。

此外,这里没有循环。你说你每100毫秒运行一次这个代码,你在这里读取的最大值为2K,所以无论你的网络连接多么饱和,你都可以读取每秒20K的最大值,对吧?这似乎对你好吗?因为这对我来说似乎不对。

答案 1 :(得分:1)

您可以使用BackGroundWorker或使用async/await两者都可以解决冻结用户界面的问题。

下面给出的代码是例如你可以google它,并使用其中一个可以解决你的问题

由于存在执行IO调用的操作,并且在使用**async的较新框架方法中可以使用async / awiat

async / await方式(新方法 - .NET 4.5 for 4.0版本,你会发现nuget)

private async void Button_Click(object sender, RoutedEventArgs 
{
      var task = Task.Factory.StartNew( () => longrunnincode());
      var items = await task;
      //code after task get completed 
}

返回地面工作者(旧方式但仍然支持并在很多旧应用程序中使用async / await在.NET 4.5 for 4.0版本中提供,你会发现nuget)

private BackgroundWorker backgroundWorker1;
this.backgroundWorker1 = new BackgroundWorker(); 
this.backgroundWorker1.DoWork += new 
        DoWorkEventHandler(this.backgroundWorker1_DoWork);

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundProcessLogicMethod();
}

private void backgroundWorker1_RunWorkerCompleted(object sender,

    RunWorkerCompletedEventArgs e)
{
  if (e.Error != null) MessageBox.Show(e.Error.Message);
    else MessageBox.Show(e.Result.ToString());
} 

private void StartButton_Click(object sender, EventArgs e)

{
    // Start BackgroundWorker
    backgroundWorker1.RunWorkerAsync(2000);
}

除非没有其他办法,否则不要将后台工作人员用于IO。直接调用异步IO API。

答案 2 :(得分:0)

所以为它做了一些方法,没有使用异步,但是我最终为这个任务创建了一个不同的线程。

新线程让我使用while(true)循环来永久循环接收响应。

private void btnConnect_Click(object sender, EventArgs e)
    {
        ConnectToServer();
        Thread timerThread = new Thread(StartTimer);
        timerThread.Start();
        //StartTimer();
        //timerResponse.Enabled = true;

    }
    private static void StartTimer()
    {
        while (true)
        {
            ReceiveResponse();
        }
    }

这足以满足我的需要。

谢谢大家的帮助!

答案 3 :(得分:-1)

在运行此代码的上下文中更为重要。您可能正在UI线程上运行它。您应该将繁重的工作推迟到另一个工作线程或使该方法异步。

答案 4 :(得分:-1)

这是GUI的常见问题。事件应尽快完成。当一个事件没有返回时,不能运行其他事件。甚至不会执行使用上次更改更新UI的代码。并且所有Windows都可以告诉用户该程序是"没有响应" (因为它还没有知道最后一个输入Windows发送它的方式)。

您需要做的是将长时间运行的操作转换为某种形式的多任务处理。有很多方法可以做到这一点,一些涉及多个线程,一些不涉及。当在GUI环境中开始使用多任务处理时(这是理想的情况),我会使用BackgroundWorker。他不是你希望在生产代码/后期使用的东西,但它是一个很好的初学者工具,学习多任务的特性(竞争条件,异常处理,回调)。

由于通常不知道网络操作受CPU限制,因此从长远来看,您可能希望采用无线程的多任务方式。

答案 5 :(得分:-1)

听起来它可能是计时器。如果您使用$process = new Process('diff'); $process->setInput('(ssh user@remote1 "cat /my/remote1/file.txt")'); $process->setInput('(ssh user@remote2 "cat /my/remote2/file.txt")'); $process->run(); $output = $process->getOutput(); 方法,这肯定是问题所在。使用此函数会锁定当前线程,并且由于您的函数未与UI线程异步运行,因此UI也将冻结。

要解决此问题,您只需将您提供的代码添加到异步任务中,并使用Thread.Sleep()方法确保该函数每100ms执行一次。

有关详细信息,请参阅this question