【问题标题】:How to handle UI freeze while working with BackgroundWorker使用 BackgroundWorker 时如何处理 UI 冻结
【发布时间】:2014-12-16 10:07:54
【问题描述】:

我正在创建一个应用程序(logviewer),它将显示来自 xml 文件的日志。 注意:每个 xml 文件可以有一个或多个记录。我的应用程序必须在 DatagridView 控件中将每条记录显示为一行。

基本上它将执行以下任务:

1.from Do_Work => 解析每个 xml 文件并将记录添加到列表中。
2.如果列表大小达到100,则调用ProgressChanged事件以更新100条记录的UI(DataGridView)。
3.重复此过程,直到所有xml文件中的所有记录都附加到UI(DataGridView)

要求: 即使用户尝试读取数千个文件,UI 也不应冻结。

我通过在DoWork 事件中等待100 毫秒然后调用ProgressChanged 来实现上述场景,原因如下:

1.后台线程等待(Thread.Sleep(100))100毫秒,以便UI线程可以同时更新并且对用户可见(记录正在追加)。

我是否需要Thread.Sleep() 才能使 UI 线程呈现记录。 有什么最好的方法可以让我在不冻结的情况下更新 UI?

因为如果我用 4000 条记录测试我的应用程序,那么 100 毫秒的等待时间也不起作用,我的意思是如果我在表单上执行一些操作,应用程序就会冻结。

但如果我将等待时间增加到 500 毫秒,那么它可以工作,但显示所有记录需要更多时间。

所以请建议我在使用 BackgroundWorker 组件时更新 UI 的更好方法。

这是我的代码:

注意:这是示例代码

    private void bWorkerReadXmlLogs_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {                               
            //declare a xmlLogRecords list to hold the list of log records to be displayed on the UI
            List<XmlLogRecord> lstXmlLogRecords = new List<XmlLogRecord>();

            //loop through the records sent by the GetLogDetails() function and add it to the list
            foreach (XmlLogRecord record in GetLogDetails(path))
            {
                //cancel the background thread if the cancel was requested from the user.
                if (bWorkerReadXmlLogs.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }
                //add log record to the list
                lstXmlLogRecords.Add(record);

                /*if the list contains 100 items then invoke the progresschanged event 
                where it appends the 100 items into the LogViewer UI (DataGridView)*/
                if (lstXmlLogRecords.Count % 100 == 0)
                {
                    //block/wait on background thread so that processor allocates some cycles to work on UI/Main thread to update the records on the DatagridView
                    Thread.Sleep(100); //if i wait more time like 500 ms then UI does not freeze but it takes more time

                    bWorkerReadXmlLogs.ReportProgress(0, new List<XmlLogRecord>(lstXmlLogRecords));

                    //clear the List to start/add items from the beginning.
                    lstXmlLogRecords.Clear();
                }
            }

            //invoke the ProgressChanged Event for updating the DataGridView if the List contains less than 100 items greater than 0 items
            if (lstXmlLogRecords.Count > 0)
            {                
                //Invoke ProgressChanged Event to update the records on the DataGridView
                bWorkerReadXmlLogs.ReportProgress(0, lstXmlLogRecords);
            }
        }
        catch (Exception ex)
        {
            Write_Trace(ex.Message);
        }
    }

    private void bWorkerReadXmlLogs_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {           
            try
            {
                var rowIndex = 0;
                if (e.UserState is List<XmlLogRecord>)
                {
                    //Cast the UserState object into the List<XmlLogRecord>
                    var records = e.UserState as List<XmlLogRecord>;

                    //iterate over all the records sent from DoWork event and append the each record into the LogViewer control DataGridView UI
                    foreach (var record in records)
                    {
                        //get the next row Index where the new row has to be placed.
                        rowIndex = dgvLogViewer.Rows.Count;

                        //add an emtpy row to add the empty cells into it
                        dgvLogViewer.Rows.Add();

                        //set the LogViewer properties if not set already
                        if (!IsLogviewerPropertiesSet)
                        {
                            SetLogViewerProperties();
                        }

                        //Disable the Column selection for image columns
                        DisableImageColumnsSelection(rowIndex);

                        //Add Data for normal or text cells into the LogViewer Control (DatagridView)
                        AddLogviewerTextCells(rowIndex, record);

                        //Add Icons in Image Columns into the LogViewer control (DataGridView) 
                        AddLogviewerImageCells(rowIndex, record);
                    }

                    //Sort the LogViewer control (DataGridView) by Datetime column(Index = 2) in Descending order.
                    dgvLogViewer.Sort(dgvLogViewer.Columns[MKeys.DTTIME], ListSortDirection.Descending);
                    dgvLogViewer.Columns[MKeys.DTTIME].HeaderCell.SortGlyphDirection = SortOrder.Descending;
                    if (!IsLogviewerSortingDone)
                    {
                        //call selectedindex changed event of the selected record in the datagridview
                        dgvLogViewer_SelectionChanged(null, null);
                        IsLogviewerSortingDone = true;
                    }
                }
            }
            catch (Exception ex)
            {                   
                Write_Trace(ex.Message);
            }
        }
    }

【问题讨论】:

  • BackgroundWorker.ProgressChanged 是做什么的?您发布的代码与 UI 无关,除了 bWorkerReadXmlLogs.ReportProgress
  • @SriramSakthivel:它将遍历所有 100 条记录并将每条记录附加到 DataGridView
  • 我建议您在更新网格时使用DataGridView.SuspendLayout
  • 你能把那个代码贴出来吗?这确实是最重要的。
  • @bansi SuspendLayout 不是为了那个目的。其目的是在进行批量更新时暂停子控件的重新排列。但是DatagridView 没有孩子。

标签: c# multithreading winforms datagridview backgroundworker


【解决方案1】:

您不需要线程睡眠。如果您触发 ProgressChanged,您可以将这 100 条记录加载到您的 GridView 数据源。然后使用 GridView 中的 Update-Methods。

  1. 向绑定到 GridView 的数据源添加新的 100 条记录。

然后:

//这里可以重新设置DataSource

GridView.Refresh();

如果这不起作用,请查看 BeginInvoke() 和 EndInvoke() 方法。

【讨论】:

    【解决方案2】:

    如果您使用 .NET Framework 4.5 或更高版本,我使用基于任务的异步模式命令并利用 async 和 await 关键字 (http://msdn.microsoft.com/en-us/library/hh191443.aspx)。但如果不是,你真的不需要任何线程睡觉。只需处理 BackgroundWorker 的 ProgressChanged 事件并更新网格。

    【讨论】:

    • 没关系,别管用Thread.Sleep了,UI线程和BackgroundWorker其实在不同的线程,不用担心UI卡死。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-02
    • 1970-01-01
    • 2013-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多