C#的耗时程序不卡UI线程
结合 C# 场景:
| 类型 | 谁等结果? | 线程是否挂起? | 举例 |
|---|---|---|---|
| 同步阻塞 | 调用方主动等 | ✅ 是(线程卡住) | Thread.Sleep(5000) 在 UI 线程 |
| 同步非阻塞 | 调用方主动轮询 | ❌ 否(但要不断检查) | 手动 while (!task.IsCompleted) { DoOther(); } |
| 异步阻塞 | 系统通知你,但你仍用 .Wait() 去等 |
✅ 是(看似异步实则阻塞) | task.Wait() 或 task.Result |
| 异步非阻塞 | 系统通知你,你用回调或 await 继续 |
❌ 否(线程释放) | await Task.Run(...) |
await Task.Run(() => { Thread.Sleep(5000); // 耗时操作 }); label1.Text = "完成";
“同步非阻塞”在 C# 里长这样,不推荐
// 同步非阻塞(反面教材) var task = Task.Run(() => HeavyWork()); while (!task.IsCompleted) { Application.DoEvents(); // 强行让 UI 刷新(WinForms 黑科技) Thread.Sleep(10); // 小睡一下避免 CPU 爆炸 } label1.Text = "完成";
若在执行耗时任务时,使用异步非阻塞,执行完后还想把执行后的状态结果,更新到UI界面上(其他线程)
方法一:获得UI线程上下文,给耗时方法传进去(或设置成全局变量),在耗时方法执行后改界面值。
public partial class Form1 : Form { SynchronizationContext _uiContext;//UI界面上下文内容 public Form1() { InitializeComponent(); _uiContext = SynchronizationContext.Current; } private void btn_b1_Click(object sender, EventArgs e) { Task.Run(() => { this.test01(); }); } //模拟耗时操作 public void test01() { Thread.Sleep(5000); //btn_b1.Text = "OK";//报错 _uiContext?.Post(_ => { // 安全检查! if (!IsDisposed && !Disposing) {btn_b1.Text = "ok";} }, null); } }
public partial class Form1 : Form { SynchronizationContext _uiContext; public Form1() { InitializeComponent(); _uiContext = SynchronizationContext.Current;//UI界面上下文内容 } private void btn_b1_Click(object sender, EventArgs e) { _uiContext = SynchronizationContext.Current; Task.Run(() => { this.test01(_uiContext); }); } //模拟耗时操作 public void test01(SynchronizationContext ctx) { Console.WriteLine("耗时操作开始...."); Thread.Sleep(5000); Console.WriteLine("耗时操作结束...."); //btn_b1.Text = "OK";//报错 ctx?.Post(_ => { // 安全检查! if (!IsDisposed && !Disposing) {btn_b1.Text = "ok";} }, null); } }
方法二:用 IProgress<T> + Progress<T> 专门用于从后台任务向 UI 报告进度或状态!
public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btn_b1_Click(object sender, EventArgs e) { // 1. 创建 Progress 对象(在 UI 线程创建!) var progress = new Progress<string>(value => { // 这个 lambda 会在 UI 线程执行!安全更新控件 btn_b1.Text = value; }); Task.Run(() => { this.test01(progress); }); } //模拟耗时操作 public void test01(IProgress<string> progress) { Thread.Sleep(5000); //btn_b1.Text = "OK";//报错 progress?.Report($"OK");// 报告当前状态(线程安全!) } }
---------------------------------
例子,C# ,判断服务器80端口是否通畅,然后把结果更新到UI界面上。(两种方式,一种是true,false,结果更新到UI界面,另一种是各种信息更新到UI界面)
不对的写法:
{ var progress = new Progress<string>(value =>{ lbl_network.Text = value;}); Task.Run(() => { bool isAccessible = NetworkUtil.CheckPortAccessibility("a.cn", 80, 5000); if (isAccessible) { //progress?.Report($"服务器正常链接.");//写法有错误,progress要作为参数传到上面耗时方法中,方法里面可以使用 lbl_network.Text = "服务器正常链接";//写法错误 Console.WriteLine($"服务器正常链接"); }else{ //progress?.Report($"服务器断开中....."); lbl_network.Text = "服务器正常链接";//写法错误 Console.WriteLine($"服务器断开中..."); } }); }
改进后的写法:
{ bool isAccessible = await Task.Run(() => {
return NetworkUtil.CheckPortAccessibility("a.cn", 80, 5000);
});
if (isAccessible)
{
lbl_network.Text = "服务器正常链接.";//修改UI界面
Console.WriteLine($"服务器正常链接");
} else {
lbl_network.Text = "服务器断开中....";//修改UI界面
Console.WriteLine($"服务器断开中...");
}
}
另一种是各种状态更新到UI界面:那就是给耗时方法中传IProgress
var progress = new Progress<string>(value =>{ lbl_network.Text = value;}); Task.Run(() => { bool isAccessible = Bxlz.Common.NetworkUtil.CheckPortAccessibility("a.cn", 80, 5000, progress); });
然后给下面方法多加一个参数,最后方法里面使用 progress?.Report($"状态...");
判断服务器80端口的耗时方法:
/// <summary> /// 检查指定主机和端口是否可访问 /// </summary> /// <param name="host">目标主机</param> /// <param name="port">目标端口</param> /// <param name="timeoutMs">超时时间(毫秒)</param> /// <returns>是否可访问</returns> public static bool CheckPortAccessibility(string host, int port, int timeoutMs) { try { // 检查网络是否可用 if (!NetworkInterface.GetIsNetworkAvailable()) { Console.WriteLine("网络不可用"); return false; } // 尝试解析DNS IPHostEntry hostEntry; try { hostEntry = Dns.GetHostEntry(host); Console.WriteLine($"DNS解析成功: {hostEntry.AddressList[0]}"); } catch (SocketException se) { Console.WriteLine($"DNS解析失败: {se.SocketErrorCode}"); return false; } // 尝试连接到服务器 using (var client = new TcpClient()) { client.ReceiveTimeout = timeoutMs; client.SendTimeout = timeoutMs; // 使用异步连接 IAsyncResult result = client.BeginConnect(hostEntry.AddressList[0], port, null, null); bool success = result.AsyncWaitHandle.WaitOne(timeoutMs, true); if (success) { try { client.EndConnect(result); if (client.Connected) { Console.WriteLine("TCP连接成功"); using (var stream = client.GetStream()) { // 发送HTTP HEAD请求 string request = $"HEAD / HTTP/1.0\r\nHost: {host}\r\nConnection: close\r\n\r\n"; byte[] requestData = Encoding.ASCII.GetBytes(request); stream.Write(requestData, 0, requestData.Length); Console.WriteLine("请求数据发送成功"); // 尝试读取响应 byte[] buffer = new byte[1024]; try { IAsyncResult readResult = stream.BeginRead(buffer, 0, buffer.Length, null, null); if (readResult.AsyncWaitHandle.WaitOne(5000, true)) // 5秒读取超时 { int bytesRead = stream.EndRead(readResult); if (bytesRead > 0) { string response = Encoding.ASCII.GetString(buffer, 0, bytesRead); Console.WriteLine($"收到响应数据长度: {bytesRead} 字节"); Console.WriteLine($"响应内容前100字符: {response.Substring(0, Math.Min(100, response.Length))}"); // 检查是否是有效的HTTP响应 if (response.StartsWith("HTTP/1.") && response.Contains("200")) { Console.WriteLine("收到有效的HTTP响应"); return true; } else { Console.WriteLine("收到非200状态的HTTP响应"); return false; } } else { Console.WriteLine("未收到任何响应数据"); return false; } } else { Console.WriteLine("读取响应超时"); return false; } } catch (Exception readEx) { Console.WriteLine($"读取响应时发生异常: {readEx.Message}"); return false; } } } else { Console.WriteLine("连接未建立"); return false; } } catch (SocketException se) { Console.WriteLine($"结束连接时发生异常: {se.SocketErrorCode}"); return false; } } else { Console.WriteLine("连接超时"); return false; } } } catch (SocketException se) { Console.WriteLine($"Socket异常: {se.SocketErrorCode} - {se.Message}"); return false; } catch (Exception ex) { Console.WriteLine($"其他异常: {ex.Message}"); return false; } }

浙公网安备 33010602011771号