异步函数冻结 UI 线程
Posted
技术标签:
【中文标题】异步函数冻结 UI 线程【英文标题】:Async function freezes UI thread 【发布时间】:2021-12-24 16:29:20 【问题描述】:我有一个异步函数,当我执行它时它仍然会冻结/滞后 UI 线程。这是我调用它的函数。
private void TcpListenerLogic(object sender, string e)
Application.Current.Dispatcher.BeginInvoke((Action)async delegate
try
dynamic results = JsonConvert.DeserializeObject<dynamic>(e);
if (results.test_id != null)
// Get properties for new anchor
string testInformation = await CommunicationCommands.getJsonFromURL(
"http://" + ServerIP + ":" + ServerPort + "/api/" + results.test_id);
catch (Exception exception)
// Writing some Trace.WriteLine()'s
);
这是冻结我的 UI 线程的异步函数
public static async Task<string> getJsonFromURL(string url)
try
string returnString = null;
using (System.Net.WebClient client = new System.Net.WebClient())
returnString = await client.DownloadStringTaskAsync(url);
return returnString;
catch (Exception ex)
Debug.WriteLine(ex.ToString());
return null;
我已经尝试让 TcpListenerLogic
中的所有内容在新的 Thread
中运行:
new Thread(() =>
Thread.CurrentThread.IsBackground = true;
).Start();
这导致整个 UI 完全冻结。我试图使TcpListenerLogic
异步并等待调度程序,这也使一切都永久冻结。我还尝试使TcpListenerLogic
异步并离开调度程序。调度程序只是在那里,因为我通常在那里有一些 UI 代码,我在测试时忽略了这些代码。
我已经在互联网上冒险了很长时间,但没有BackgroundWorker
、ThreadPool
或其他方法帮助我努力。
如果有人对这个特定问题有帮助,或者有资源可以提高我对 C# 中异步函数的理解,我将不胜感激。
编辑
应要求更深入地了解如何调用此事件处理程序。 我有 System.Net.Websocket,它连接到我正在使用的后端 API,并在他每次收到新数据时触发一个事件。为了保证套接字在打开时一直监听,有一个 while 循环来检查客户端状态:
public event EventHandler<string> TcpReceived;
public async void StartListener(string ip, int port, string path)
try
using (client = new ClientWebSocket())
try
// Connect to backend
Uri serverUri = new Uri("ws://" + ip + ":" + port.ToString() + path );
await client.ConnectAsync(serverUri, CancellationToken.None);
catch (Exception ex)
BackendSettings.IsConnected = false;
Debug.WriteLine("Error connecting TCP Socket: " + ex.ToString());
state = client.State;
// Grab packages send in backend
while (client.State == WebSocketState.Open || client.State == WebSocketState.CloseSent)
try
// **Just formatting the received data until here and writing it into the "message" variable**//
TcpReceived(this, message);
// Close connection on command
if (result.MessageType == WebSocketMessageType.Close)
Debug.WriteLine("Closing TCP Socket.");
shouldstayclosed = true;
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
state = client.State;
catch
BackendSettings.IsConnected = false;
state = client.State;
state = client.State;
catch (Exception ex)
// Some error messages and settings handling
事件附加了一个处理程序:
TcpReceived += TcpListener_TcpReceived;
这就是Handler,它调用之前看到的“TcpListenereLogic”。
private void TcpListener_TcpReceived(object sender, string e)
TcpListenerLogic(sender, e);
//App.Current.Dispatcher.BeginInvoke(new Action(() =>
// TcpListenerLogic(sender, e);
//));
//new Thread(() =>
//
// Thread.CurrentThread.IsBackground = true;
// TcpListenerLogic(sender, e);
//).Start();
我以前使用“TcpListenereLogic”作为处理程序,但我想尝试不同的方法来调用它。我还留在了注释掉的部分,以展示“TcpListenereLogic”的调用看起来如何。我所有的尝试都使用了所有提到的设置,遗憾的是没有任何结果。
【问题讨论】:
await
不会自动启动新任务,这就是您的 UI 仍然冻结的原因。使用Task.Run
。你可能想通读this的答案。
TcpListenerLogic
方法是如何以及在哪里调用的?
^^ 是事件处理器吗?
我建议的第一件事是不使用 WebClient。
并非所有异步方法都是非阻塞的,即使它们应该是。我还建议查看Task based asynchronous programming,了解在后台运行的现代方式。
【参考方案1】:
非常感谢@TheodorZoulias 帮助我找到解决问题的方法。
原来这不是异步函数本身,而是它被调用的频率。它每秒大约被调用约 120 次。 我的解决方案首先在新线程上调用 Listener 方法:
new Thread(() =>
Thread.CurrentThread.IsBackground = true;
MainWindow.tcpListener.StartListener(ip, portNumber, "/api/");
).Start();
为了限制每秒发生的调用量,我添加了一个调度程序计时器,它在我的事件用于调用后重置一个布尔值。
readonly System.Windows.Threading.DispatcherTimer packageIntervallTimer =
new System.Windows.Threading.DispatcherTimer();
bool readyForNewPackage = true;
private void ReadyForPackage(object sender, EventArgs e)
readyForNewPackage = true;
public async void StartListener(string ip, int port, string path)
packageIntervallTimer.Interval = TimeSpan.FromMilliseconds(50);
packageIntervallTimer.Tick += (s, e) => Task.Run(() => ReadyForPackage(s, e)); ;
packageIntervallTimer.Start();
然后我将 while 循环中的所有内容包装成一个基于布尔值的 if 条件,最重要的部分是在其中包含我的“事件 EventHandler TcpReceived”:
// Grab packages sent in backend
while (client.State == WebSocketState.Open || client.State == WebSocketState.CloseSent)
if (readyForNewPackage == true)
readyForNewPackage = false;
try
....
TcpReceived(this, message);
....
catch
...
我将我的 TcpListenerLogic 添加到事件处理程序中:
TcpReceived += TcpListenerLogic;
我的 TcpListenerLogic 现在看起来像这样(名称已更改):
private async void TcpListenerLogic(object sender, string e)
try
dynamic results = JsonConvert.DeserializeObject<dynamic>(e);
if (results.test_id != null)
string testID = "";
if (results.test_id is JValue jValueTestId)
testID = jValueTestId.Value.ToString();
else if (results.test_id is string)
testID = results.test_id;
// Get properties for new object
string information = await CommunicationCommands.getJsonFromURL(
"http://" + ServerIP + ":" + ServerPort + "/api/" + testID );
if (information != null)
await App.Current.Dispatcher.BeginInvoke(new Action(() =>
// Create object out of the json string
TestStatus testStatus = new TestStatus();
testStatus.Deserialize(information);
if (CommunicationCommands.isNameAlreadyInCollection(testStatus.name) == false)
// Add new object to the list
CommunicationCommands.allFoundTests.Add(testStatus);
));
catch (Exception exception)
....
添加一个新线程来执行任何步骤都会导致问题,所以请记住,所有这些都使用在开头为“StartListener”创建的线程
【讨论】:
以上是关于异步函数冻结 UI 线程的主要内容,如果未能解决你的问题,请参考以下文章
如何在Android开发中用AsyncTask异步更新UI界面