我为我的大学学位创建了一个远程管理工具。我目前真的陷入了我的代码中的一个错误,并想知道是否有人可以解释我的问题。
我有应用程序,服务器和客户端。服务器运行正常。然而,客户端是冻结的应用程序。
在连接到服务器之前,客户端工作正常。连接到服务器后,客户端将一直冻结在屏幕上。
我已经将错误缩小到特定的代码段,而运行应用程序的代码并没有冻结。但是,该应用程序也不起作用。
以下是该代码的示例:
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之外,应用程序上的所有内容都可以正常工作。
这真的令人沮丧,而我无法确定代码会导致应用程序冻结。
非常感谢任何帮助。
谢谢提前。
答案 0 :(得分:3)
到目前为止有六个答案,没有任何有用,正确或可操作的建议。忽略它们。
我已经将错误缩小到特定的代码段,而运行应用程序的代码没有冻结
好,这是诊断问题的第一步。 更进一步。这里究竟哪些方法具有高延迟?我的猜测是Receive
或Read
,但有几个可能的候选人,可能不止一个。
一旦确定了每种高延迟方法,确定它是否很慢,因为它正在使用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。