2 проблемы с компонентом BackgroundWorker

Во-первых, я знаю, что должен использовать надлежащие методы обработки потоков (Threadpool, BeginInvoke и т. д.), чтобы выполнить это, но в настоящее время это немного выше моего понимания, и мне потребуется некоторое время, чтобы прочитать материал и понять это (если у вас есть какие-либо URL-ссылки на мой сценарий, пожалуйста, опубликуйте их).

Тем временем я использую backgroundWorker для извлечения очень большого набора результатов и заполнения им DatagridView. Я успешно создаю SortableBindingList<TEntities> в своем событии DoWork и передаю его в результате. И в событии RunWorkerCompleted я разыгрываю и привязываю это SortableBindingList<TEntities> к своей сетке. Мои 2 основные области беспокойства заключаются в следующем:

1) Доступ к закрытым переменным. Я хочу передать один из двух параметров List<long> в свое событие DoWork, но запустить другой запрос в зависимости от того, какой список ему был передан. Я могу обойти это, объявив частную логическую переменную на уровне класса, которая действует как своего рода флаг. Это кажется глупым спрашивать, но в моем DoWork можно ли мне получить доступ к этой частной переменной и соответствующим образом направить запрос? (Я проверил это, и это работает, без каких-либо ошибок)

private bool SearchEngaged = false;

private void bgw_DoWork(object sender, DoWorkEventArgs e) {
    BackgroundWorker worker = sender as BackgroundWorker;
    e.Result = GetTasks((List<long>)e.Argument, worker, e);
}
SortableBindingList<Task> GetTasks(List<long> argsList, BackgroundWorker worker, DoWorkEventArgs e) {
    SortableBindingList<Task> sbl = null;
    if (worker.CancellationPending) {
        e.Cancel = true;
    }
    else {
        if (SearchEngaged) {
            sbl = DU.GetTasksByKeys(argsList);
        }
        else {
            sbl = DU.GetTasksByDivision(argsList);
        }
    }
    return sbl;
}

2) Поток пользовательского интерфейса зависает в начале RunWorkerCompleted. Хорошо, я знаю, что мой пользовательский интерфейс реагирует во время события DoWork, потому что требуется +/- 2 секунды для запуска и возврата моего SortableBindingList<Task>, если я не привязывайте список к сетке, а просто заполняйте его. Однако мой пользовательский интерфейс зависает, когда я привязываю его к сетке, что я делаю в событии RunWorkerCompleted. Имейте в виду, что моя сетка имеет 4 столбца изображений, которые я обрабатываю в CellFormatting. Этот процесс занимает еще 8 секунд, в течение которых мой пользовательский интерфейс полностью неинтерактивен. Я знаю о последствиях этого для нескольких потоков, но есть ли способ выполнить заполнение и форматирование Grid в фоновом режиме или без зависания пользовательского интерфейса? RunWorkeCompleted выглядит так:

private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
    if (e.Cancelled) {
        lblStatus.Text = "Operation was cancelled";
    }
    else if (e.Error != null) {
        lblStatus.Text = string.Format("Error: {0}", e.Error.Message);
    }
    else {
        SortableBindingList<Task> sblResult = (SortableBindingList<Task>)e.Result;
        dgv.DataSource = sblResult;
        dgv.Enabled = true;
        TimeSpan Duration = DateTime.Now.TimeOfDay - DurationStart;
        lblStatus.Text = string.Format("Displaying {0} {1}", sblResult.Count, "Tasks");
        lblDuration.Visible = true;
        lblDuration.Text = string.Format("(data retrieved in {0} seconds)", Math.Round(Duration.TotalSeconds, 2));
        cmdAsyncCancel.Visible = false;
        tmrProgressUpdate.Stop();
        tmrProgressUpdate.Enabled = false;
        pbStatus.Visible = false;
    }
}

Извините за длинный вопрос, но я буду очень признателен за ваши ответы! спасибо!


person Shalan    schedule 26.11.2009    source источник


Ответы (2)


Ваш код, похоже, делает то, что нужно.

Что касается 8 секунд, которые требуются потоку пользовательского интерфейса для обновления экрана, вы мало что можете с этим поделать. См. мой ответ на этот вопрос.

Чтобы оптимизировать часть пользовательского интерфейса, вы можете попробовать вызвать SuspendLayout и ResumeLayout на сетке или содержащей ее панели.

Вы также можете попытаться уменьшить объем обработки, выполняемой во время привязки данных. Например:

  • Расчеты, выполненные в сетке, можно было перенести в модель данных (таким образом, выполняя их в рабочем потоке).
  • Если сетка автоматически вычисляет свои столбцы на основе модели данных, попробуйте вместо этого жестко закодировать их.
  • РЕДАКТИРОВАНИЕ: разверните данные на бизнес-уровне и заставьте сетку отображать только небольшое количество строк за раз.
person Christian Hayter    schedule 26.11.2009
comment
Эй, христианин! спасибо за ваш вклад! вызов SuspendLayout и ResumeLayout не совсем решает мою ситуацию. Расчетов как таковых нет - больше форматирование ячеек. И единственное место, где я могу это сделать, — это событие CellFormatting сетки. Я рассматриваю 2 обходных пути. - person Shalan; 26.11.2009
comment
ONE: Запускаю мой запрос как обычно в DoWork, и для каждого объекта, возвращаемого в списке, я могу создать и заполнить DataGridViewRow и передать его в событие ProgressChanged, где затем я могу добавить эту строку в сетка (не знаю, стоит ли это дополнительной обработки и возможно ли это вообще!) - person Shalan; 26.11.2009
comment
ВТОРОЕ: я знаю, что это противоречит всем передовым практикам в отношении сеток winforms, но в моих обстоятельствах я думаю, что, возможно, стоит расширить сетку, чтобы облегчить пейджинг. Ив заметил, что если я верну только 100 строк, сетка закрасится за 0,4 секунды. Твои мысли? - person Shalan; 26.11.2009
comment
Re: ONE - не лучшая идея. Вы будете заполнять большой изменяемый объект, связанный с пользовательским интерфейсом, в одном потоке, а затем передавать его другому потоку для рендеринга. Это запах многопоточного кода, и я бы не рекомендовал его. - person Christian Hayter; 26.11.2009
comment
Re: ДВА - Хорошая идея. Это сделало бы пользовательский интерфейс более масштабируемым. Пейджинг сам по себе никогда не является проблемой, именно там, где вы выполняете пейджинг, вы должны наблюдать. Возврат 100 строк с бизнес-уровня = хорошо, возврат 100000 строк с бизнес-уровня, а затем разбивка на страницы на уровне представления = плохо. - person Christian Hayter; 26.11.2009
comment
Да, я больше склоняюсь к варианту ВТОРОЙ. Я согласен с вами, что возвращать слишком много из BL — это плохая практика. Однако учтите следующее: я извлекаю и заполняю List‹TEnties› 5013 объектами за 0,31 секунды (это ясно видно, если я запускаю существующий код, комментируя строку: dgv.DataSource = sblResult; да - добавление ~ 7 секунд для рисования сетки) Если я сейчас назначу возвращенный sblResult частной переменной класса, не облегчит ли это сортировку и разбиение по страницам более эффективно? - person Shalan; 26.11.2009
comment
Я могу ошибаться с этим, поскольку это всего лишь предположение... Если я возвращаю только 100 строк из BL, а затем сортирую столбец, я сортирую только эти 100 строк, а не весь набор результатов. Может показаться тяжелым и ненужным возвращать всю партию, но более плавный UX предпочтительнее, чем то, что может быть истолковано как хорошая практика логического дизайна. Извините, я не оспариваю ваше мнение по этому поводу ... просто делюсь своим в надежде на лучшее понимание и получение компромиссного решения. - person Shalan; 26.11.2009
comment
Да, в этом случае сохранение sblResult в частной переменной класса было бы хорошим компромиссом. Вы можете выполнить пейджинг на уровне сетки по всему набору результатов. - person Christian Hayter; 26.11.2009
comment
Если вы выполняете пейджинг в BL, вам придется все делать самому. Если пользователь меняет порядок сортировки, вам придется вернуться к BL, повторно запросить данные с новым порядком сортировки, а затем повторно получить первую страницу новых данных. - person Christian Hayter; 26.11.2009
comment
ТОЧНО!! +1 2 ты! заполнение и затем работа с «локальным» представлением данных приведет к более удобному интерфейсу imo. Как я понял, что вы подтвердили в своем комментарии выше, я действительно думаю, что это потребует больших затрат на обработку, не говоря уже о задержках из-за постоянных транзакций по сети с вероятной задержкой. - person Shalan; 26.11.2009
comment
кроме того, я использую LINQ, поэтому возврат списка и использование Skip() и Take() будут работать в мою пользу - person Shalan; 26.11.2009

Я думаю, что самым простым решением вашей проблемы является установка источника данных вашей сетки в DoWork вместо RunWorkerCompleted с использованием Dispatcher.BeginInvoke, о котором вы сами упомянули. Что-то вроде этого:

private bool SearchEngaged = false;

private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    SortableBindingList<Task> sblResult = GetTasks((List<long>)e.Argument, worker, e);

    BeginInvoke((Action<object>)(o => dataGridView1.DataSource = o), sblResult);
}

private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled) {
        lblStatus.Text = "Operation was cancelled";
    }
    else if (e.Error != null) {
        lblStatus.Text = string.Format("Error: {0}", e.Error.Message);
    }
    else
    {
        dgv.Enabled = true;
        TimeSpan Duration = DateTime.Now.TimeOfDay - DurationStart;
        lblStatus.Text = string.Format("Displaying {0} {1}", sblResult.Count, "Tasks");
        lblDuration.Visible = true;
        lblDuration.Text = string.Format("(data retrieved in {0} seconds)", Math.Round(Duration.TotalSeconds, 2));
        cmdAsyncCancel.Visible = false;
        tmrProgressUpdate.Stop();
        tmrProgressUpdate.Enabled = false;
        pbStatus.Visible = false;
    }
}

Что касается проблемы с частной переменной, я не думаю, что в вашем случае это будет проблемой. Если вы меняете его с помощью какого-либо события пользовательского интерфейса, просто отметьте приватное поле как volatile. Документацию по ключевому слову volatile можно найти здесь:

http://msdn.microsoft.com/en-us/library/x13ttww7.aspx

person Yogesh    schedule 26.11.2009
comment
Лямбда-выражения... здорово! спасибо за ответ йогеш. Хотя sblResult отображается как null, когда я ставлю точку останова сразу после BeginInvoke, а затем запускаю приложение. Любые идеи? Dispatcher.BeginInvoke следует переписать как Dispatcher.CurrentDispatcher.BeginInvoke (WindowsBase.dll) - person Shalan; 26.11.2009
comment
Сетка вообще не заполняется. Раньше я ошибался — я вижу, что sblResult действительно содержит данные, но в сетке ничего не происходит. Есть ли что-то, что мне не хватает? - person Shalan; 26.11.2009
comment
Извините, я разместил решение, связанное с WPF. Проверьте мой пост еще раз. Отредактировано. - person Yogesh; 26.11.2009
comment
Эй, йогеш. Вы очень помогли мне с моей первой проблемой, но я боюсь, что вышеизложенное не решит мою вторую проблему. на этот раз весь мой пользовательский интерфейс блокируется с помощью курсора ожидания, и я не получал этого раньше - person Shalan; 26.11.2009