【问题标题】:Form freezes despite using BackgroundWorker unless we add a fake sleep尽管使用了 BackgroundWorker,但表单仍会冻结,除非我们添加假睡眠
【发布时间】:2014-08-22 09:22:08
【问题描述】:

我已经成功地让我的BackgroundWorker 在我的WinForm 上完成了它的工作。它工作正常,但实际上并非如此。如下所示,我的表单有一个listbox 和一个Progress Bar。我同时使用它们来显示在for loop 的文件中写入收件人列表的进度。我知道这个过程运行得太快了。每次写入一行时,listbox 都会显示一条“Adding x out of y recipients”消息,并删除其自身的最后一个条目,以免文本过多。同样,每次插入时,progress bar 都必须迈出一步。

当我使用Thread.Sleep(1) 在每次写入中添加 1 毫秒延迟时,程序运行良好。我的表单仍然可以在桌面上移动,并且表单看起来很正常。但是当我删除这个睡眠(这是真实的场景)时,表单会冻结,就好像我从来没有使用过并行的 BackgroundWorker 一样开始。我该如何克服这个问题?

这里分别是我的 BackgroundWorker 的 DoWork()ProgressChanged() 事件:

DoWork:

private void backgroundWorkerConvertDatatableToFile_DoWork(object sender, 
                                                           DoWorkEventArgs e)
{
    try
    {
        StringBuilder sb = new StringBuilder();
        IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                            Select(column => column.ColumnName);
        sb.AppendLine(string.Join(GetDelimiter(campaignOutputFormat), columnNames));
        backgroundWorkerConvertDatatableToFile.ReportProgress(-1, 
                                   dt.Rows.Count.ToString() + " recipients found.");
        for (int i = 0; i < dt.Rows.Count; i++)
        {
           IEnumerable<string> fields = dt.Rows[i].ItemArray.Select(
                                                    field => field.ToString());
           sb.AppendLine(string.Join(GetDelimiter(campaignOutputFormat), fields));
           backgroundWorkerConvertDatatableToFile.ReportProgress(i, 
            string.Format("Adding {0} of {1}...", (i + 1).ToString(), dt.Rows.Count));
           Thread.Sleep(1);
        }
        string outputFile = String.Format("{0}\\{1}.csv", 
                                            campaignOutputPath, campaignFileName);
        backgroundWorkerConvertDatatableToFile.ReportProgress(0, "Writing to file..");
        File.WriteAllText(outputFile, sb.ToString());
        convertSuccess = true;
    }
    catch (Exception ex)
    {
        logger.Log(LogLevel.Error, 
                   "FileCampaignRunner: ConvertDataTableToCSV", ex.Message);
        convertSuccess = false;
    }
    if (convertSuccess)
    {
        backgroundWorkerConvertDatatableToFile.ReportProgress(100, 
                                               "Write to file successful!");
    }
    else
    {
        backgroundWorkerConvertDatatableToFile.ReportProgress(100, 
                                               "Error writing to file.");
    }
}

ProgressChanged:

private void backgroundWorkerConvertDatatableToFile_ProgressChanged(object sender,
                                                    ProgressChangedEventArgs e)
{
    switch (e.ProgressPercentage)
    {
        case -1:
            listBoxMessages.Items.Add(e.UserState.ToString());
            listBoxMessages.Items.Add("");
            break;
        case 100:
            listBoxMessages.Items.RemoveAt(listBoxMessages.Items.Count - 1);
            listBoxMessages.Items.Add(e.UserState.ToString());
            break;
        default:
            listBoxMessages.Items.RemoveAt(listBoxMessages.Items.Count - 1);
            listBoxMessages.Items.Add(string.Format(e.UserState.ToString()));
            progressBar.PerformStep();
            break;
    }
}

【问题讨论】:

  • “冻结”是什么意思:您不能再使用它,或者它没有显示进度可能是因为它没有时间这样做?
  • @TaW 它变得没有响应。我无法拖动窗口,它变白了。

标签: c# multithreading winforms listbox backgroundworker


【解决方案1】:

当您过于频繁地调用 ReportProgress() 时,这是完全正常的。 firehose 问题,您要求 UI 线程做比它能够做的更多的工作。一旦它执行了一个委托目标,另一个委托目标就可用,它永远无法赶上。它现在停止执行其其他职责,响应输入和绘制窗口。它只是在派遣代表时燃烧 100% 的核心。调用队列不断增长,但用户在程序内存不足之前就失去了耐心,因此很少发生实际崩溃。

您需要通过减少调用 ReportProgress() 来解决此问题。请记住,您只需要实现一个目标,让用户的眼睛保持愉悦。这很容易,您的输出会变成每秒大约 20 次更新的不可读模糊。为您提供轻松的 50 倍安全边际。

【讨论】:

  • 你说的很有道理。我不太明白你的最后一点“给你一个简单的 50 倍安全边际”。你建议我做什么来平滑它?我应该每 50 次插入报告一次吗?
  • 没有。您调用 ReportProgress 的频率不应超过每秒 20 次。具体如何做取决于您自己,使用 Environment.TickCount 是一种合乎逻辑的方式,但关注 可用性 是实现目标的主要方式。提供有意义的进度反馈,用户实际上可以阅读,这个问题就会自行解决。
  • 您能给我看一个使用 Environment.TickCount 来决定是否报告进度的基本示例,以便我接受您作为正确答案吗?
【解决方案2】:

这是因为 UI 线程阻塞了执行线程(在访问 UI 控件时)。试试下面的代码

        switch (e.ProgressPercentage)
        {
            case -1:
                listBoxMessages.Invoke(new MethodInvoker(delegate
                {
                    listBoxMessages.Items.Add(e.UserState.ToString());
                    listBoxMessages.Items.Add("");
                }));
                break;
            case 100:
                listBoxMessages.Invoke(new MethodInvoker(delegate
                {
                    listBoxMessages.Items.RemoveAt(listBoxMessages.Items.Count - 1);
                    listBoxMessages.Items.Add(e.UserState.ToString());
                }));
                break;
            default:
                listBoxMessages.Invoke(new MethodInvoker(delegate
                {
                   listBoxMessages.Items.RemoveAt(listBoxMessages.Items.Count - 1);
                   listBoxMessages.Items.Add(string.Format(e.UserState.ToString()));
                }));
                progressBar.Invoke(new MethodInvoker(delegate
                {
                progressBar.PerformStep();
                }));
                break;
        }

【讨论】:

    猜你喜欢
    • 2011-04-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-05
    • 1970-01-01
    • 1970-01-01
    • 2022-07-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多