尽管使用BackgroundWorker,表单冻结,除非我们添加假睡眠

时间:2014-08-22 09:22:08

标签: c# multithreading winforms listbox backgroundworker

我已成功让BackgroundWorkerWinForm上完成工作。它工作正常,但实际上它没有。如下所示,我的表单包含listboxProgress Bar。我正在使用它们来显示在for loop的文件中写入收件人列表的进度。我知道这个过程运行得太快了。每次写入一行时,listbox都会显示“添加x个y收件人”消息并删除其自身的最后一个条目,以便不会有太多文本。每次插入时,progress bar都必须迈出一步。

enter image description here

当我使用一个Thread.Sleep(1)在每次写入中添加1ms延迟时,程序正常工作。我的表单仍然可以在桌面上移动,表单看起来很正常。但是当我删除这个睡眠时(这是真实场景),表单会冻结,好像我从未使用并行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;
    }
}

2 个答案:

答案 0 :(得分:5)

当您经常调用ReportProgress()时,这是完全正常的。一个 firehose问题,你要求UI线程做更多的工作,然后才能做到。一旦它执行了一个委托目标,那么另一个目标可用,它永远无法赶上。它现在停止执行其他职责,响应输入和绘制窗口。它只是在派遣代表时燃烧100%的核心。调用队列在不受限制的情况下不断增长,但是在程序内存耗尽之前,用户就会没有耐心,因此实际崩溃很少。

您需要通过不频繁地调用ReportProgress()来解决此问题。请记住,您只需要实现一个目标,让用户的目光得到娱乐。这很容易,你的输出变成了每秒20次更新的难以理解的模糊。为您提供50倍的安全保证金。

答案 1 :(得分:-1)

这是因为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;
        }