C# WinForm SSH.NET SFTP下载进度条不更新问题排查
SFTP下载进度条无法更新的问题
我是C#新手,表述不当之处请见谅。我尝试为SFTP下载功能实现进度条,但始终无法正常更新。参考了相关示例但没有用Task.Run(() => Download());,因为需要等待下载完成后再执行后续操作。
疑问点
- 操作哪里有误?该如何解决?
- 为什么
progressbar1的Value值无法更新?
调试时发现代码会停在DownloadProgresBar方法中的progressBar1.Invoke((MethodInvoker)delegate { progressBar1.Value = (int)bytesRead; });这一行,但进度条无变化,而Console.WriteLine("Downloaded:" + bytesRead);却能正常输出。
我的代码
private void Download(string updateDataLoc, int updateDataLocPort, string updatUsr, string updatPas, string RemPath, string LocPath, string fichero) { try { int Port = updateDataLocPort; string Host = updateDataLoc; string Username = updatUsr; string Password = updatPas; string RemotePath = RemPath; // "/remote/path/"; string SourcePath = LocPath; // @"C:\local\path\"; string FileName = fichero; // "download.txt"; string SourceFilePath = SourcePath; //includes the file name using (var stream = new FileStream(SourcePath, FileMode.Create)) using (var client = new SftpClient(Host, Port, Username, Password)) { client.Connect(); SftpFileAttributes attributes = client.GetAttributes(RemotePath + FileName); // Set progress bar maximum on foreground thread progressBar1.Invoke( (MethodInvoker)delegate { progressBar1.Maximum = (int)attributes.Size; }); // Download with progress callback client.DownloadFile(RemotePath + FileName, stream, downloadCallback: DownloadProgresBar); MessageBox.Show("Download complete"); } } catch (Exception e) { MessageBox.Show(e.Message); } } private void DownloadProgresBar(ulong bytesRead) { Console.WriteLine("Downloaded:" + bytesRead); // Update progress bar on foreground thread progressBar1.Invoke((MethodInvoker)delegate { progressBar1.Value = (int)bytesRead; }); }
问题分析与解决
核心问题:主线程被阻塞
你没把下载操作放到后台线程,导致Download方法直接在UI主线程执行。SFTP下载是耗时操作,会彻底卡住主线程——虽然你用了Invoke试图更新进度条,但主线程正忙着处理下载,根本没时间处理UI更新的消息队列,所以进度条看起来没变化,调试时会停在Invoke那一行(因为委托一直在排队等主线程空闲)。
解决方法
用async/await结合Task.Run,既能让下载在后台跑不卡UI,又能保证后续操作在下载完成后执行:
修改后的代码
// 按钮点击事件改成async,用来触发下载并等待完成 private async void btnDownload_Click(object sender, EventArgs e) { // 替换成你的参数 await Download("你的主机地址", 22, "用户名", "密码", "/远程路径/", @"C:\本地路径\", "文件名.txt"); // 下载完成后的后续操作写在这里,比如文件校验、解压等 MessageBox.Show("后续操作启动"); } private async Task Download(string updateDataLoc, int updateDataLocPort, string updatUsr, string updatPas, string RemPath, string LocPath, string fichero) { try { int Port = updateDataLocPort; string Host = updateDataLoc; string Username = updatUsr; string Password = updatPas; string RemotePath = RemPath; string SourcePath = LocPath; string FileName = fichero; // 把耗时的下载操作丢到后台线程 await Task.Run(() => { using (var stream = new FileStream(SourcePath, FileMode.Create)) using (var client = new SftpClient(Host, Port, Username, Password)) { client.Connect(); SftpFileAttributes attributes = client.GetAttributes(RemotePath + FileName); // 初始化进度条最大值和初始值 progressBar1.Invoke((MethodInvoker)delegate { progressBar1.Maximum = (int)attributes.Size; progressBar1.Value = 0; }); // 下载并触发进度回调 client.DownloadFile(RemotePath + FileName, stream, downloadCallback: DownloadProgresBar); } }); MessageBox.Show("Download complete"); } catch (Exception e) { MessageBox.Show(e.Message); } } private void DownloadProgresBar(ulong bytesRead) { Console.WriteLine("Downloaded:" + bytesRead); // 用BeginInvoke异步更新,避免等待主线程拖慢下载 progressBar1.BeginInvoke((MethodInvoker)delegate { // 加边界检查,防止bytesRead超过最大值导致报错 if (bytesRead <= progressBar1.Maximum) progressBar1.Value = (int)bytesRead; }); }
关键说明
async/await + Task.Run组合:后台线程处理下载,主线程保持响应,Invoke/BeginInvoke的UI更新委托能及时执行。- 进度条初始化:添加
progressBar1.Value = 0,避免初始值异常导致的显示问题。 - 用
BeginInvoke替代Invoke:Invoke会等待主线程执行完委托才返回,可能轻微拖慢下载;BeginInvoke是异步调用,不用等待,对下载性能更友好。 - 边界值检查:防止下载过程中
bytesRead意外超过进度条最大值,引发参数异常。
内容的提问来源于stack exchange,提问作者sigsag
相关产品推荐
相关产品推荐

