C#中BackgroundWorker多线程文本搜索ListView显示异常求助
WinForm多线程文本搜索问题排查(BackgroundWorker)
嘿,我看了你用BackgroundWorker实现文本搜索的代码,确实踩了两个典型的新手坑——行号始终为0和内容重复显示,咱们一步步来拆解问题:
问题1:行号永远是0的根源
你的linenumbercount方法逻辑完全走偏了:
- 你把行号作为参数传入,但调用后没有把返回值更新回类的
linenumber1字段,导致每次调用都传初始值0,自然永远返回0开头的数字。 - 行号的逻辑错了:行号是每读一行就递增,不管这行有没有匹配关键词,而不是只有匹配时才处理。现在的写法完全没跟上文件的行号进度。
问题2:内容重复显示的原因
- 你用类字段
line2存储匹配到的行内容,一旦某行匹配成功,line2就会保留这个值;如果下一行不匹配,CountInString会直接返回之前的line2,导致不匹配的行也带着旧内容上报给UI。 - 你不管行是否匹配,每隔一秒就上报一次进度,这会把不匹配的行(带着旧内容)也塞进ListView里,自然重复。
直接上修正后的代码吧,我把逻辑理清楚了:
首先是main_work类的核心逻辑修正
class main_work { public class ReportState { // 改个清晰的命名,避免混淆 public int LineNumber; public string LineContent; } public string SourceFile; public string CompareString; public void CountWords(System.ComponentModel.BackgroundWorker worker, System.ComponentModel.DoWorkEventArgs e) { // 这里不能直接弹MessageBox,跨线程会报错,改成抛异常在UI层处理 if (string.IsNullOrEmpty(CompareString)) { throw new InvalidOperationException("Please Enter a string to be searched"); } int currentLineNumber = 0; DateTime lastReportTime = DateTime.Now; // 控制上报频率,避免UI卡顿 int reportInterval = 100; // 毫秒 using (var reader = new System.IO.StreamReader(SourceFile)) { while (!reader.EndOfStream) { // 先检查是否要取消任务 if (worker.CancellationPending) { e.Cancel = true; return; } currentLineNumber++; string currentLine = reader.ReadLine(); // 只有匹配到关键词时才上报UI if (currentLine?.Contains(CompareString) == true) { // 控制上报频率,避免UI频繁刷新 if (DateTime.Now.Subtract(lastReportTime).TotalMilliseconds >= reportInterval) { var state = new ReportState { LineNumber = currentLineNumber, LineContent = currentLine }; worker.ReportProgress(0, state); lastReportTime = DateTime.Now; } else { // 如果不需要频率控制,直接上报就行 var state = new ReportState { LineNumber = currentLineNumber, LineContent = currentLine }; worker.ReportProgress(0, state); } } // 稍微睡一下,别占满CPU System.Threading.Thread.Sleep(1); } } } }
然后是Form里的ProgressChanged事件修正
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { var state = (main_work.ReportState)e.UserState; // 虽然BackgroundWorker的ProgressChanged在UI线程,但加个Invoke检查更稳妥 if (listView1.InvokeRequired) { listView1.Invoke(new Action(() => AddSearchResult(state))); } else { AddSearchResult(state); } } // 把添加ListView项的逻辑抽出来,更清晰 private void AddSearchResult(main_work.ReportState state) { var item = new ListViewItem(state.LineNumber.ToString()); item.SubItems.Add(state.LineContent); listView1.Items.Add(item); }
最后补个RunWorkerCompleted里的异常处理修正
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Error != null) { MessageBox.Show("Error: " + e.Error.Message); } else if (e.Cancelled) { MessageBox.Show("Search canceled."); } else { MessageBox.Show("Search finished."); } }
几个额外的小提醒:
- 绝对不要在DoWork里操作UI:之前你在
CountWords里弹MessageBox,这会直接触发跨线程异常,一定要通过RunWorkerCompleted或者ProgressChanged来处理UI操作。 - 命名规范很重要:把模糊的变量名(比如
line2、LinesCounted1)改成有意义的名字,后续维护起来会轻松很多。 - 取消操作要及时:在循环里检查
CancellationPending后要立刻返回,确保线程能快速终止。
内容的提问来源于stack exchange,提问作者user6187871
相关产品推荐
相关产品推荐

