Вы здесь

Правильная индикация прогресса цикла

tags: 

Так как индикатор представляет собой элемент графического интерфейса пользователя, то любая операция с ним выполняется относительно долго, будь то строка состояния, индикатор или даже надпись. Поэтому в случае большого количества проходов цикла обновление индикатора на каждом проходе может стать главным потребителем вычислительных ресурсов компьютера. Хотя такие ситуации легко обнаруживаются с помощью замера производительности, все же лучше сразу предусматривать подобные проблемы.

Привожу 2 вспомогательных метода, которые позволят просто и эффективно реализовать индикацию любого цикла с известным максимальным значением счетчика.

// Получает структуру для индикации прогресса цикла.
//
// Параметры:
//  КоличествоПроходов – Число - максимальное значение счетчика;
//  ПредставлениеПроцесса – Строка, "Выполнено" – отображаемое название процесса;
//  ВнутреннийСчетчик - Булево, *Истина - использовать внутренний счетчик с начальным значением 1,
//                    иначе нужно будет передавать значение счетчика при каждом вызове обновления индикатора;
//  КоличествоОбновлений - Число, *100 - всего количество обновлений индикатора;
//  ЛиВыводитьВремя - Булево, *Истина - выводить приблизительное время до окончания процесса;
//  РазрешитьПрерывание - Булево, *Истина - разрешает пользователю прерывать процесс.
//  МинимальныйПериодОбновления - Число, *1 - с, обновлять не чаще чем этот период, 0 - по количеству обновлений,
//                    эта реализация не поддерживает дробные значения;
//
// Возвращаемое значение:
//  Структура - которую потом нужно будет передавать в метод ЛксОбработатьИндикатор.
//
Функция ЛксПолучитьИндикаторПроцесса(Знач КоличествоПроходов = 0, ПредставлениеПроцесса = "Выполнение", ВнутреннийСчетчик = Истина,
    Знач КоличествоОбновлений = 100, ЛиВыводитьВремя = Истина, РазрешитьПрерывание = Истина, МинимальныйПериодОбновления = 1) Экспорт

    Индикатор = Новый Структура;
    Если КоличествоПроходов = 0 Тогда
        Состояние(ПредставлениеПроцесса + "...");
        КоличествоПроходов = 1;
    КонецЕсли;
    Индикатор.Вставить("КоличествоПроходов", КоличествоПроходов);
    Индикатор.Вставить("ПредставлениеПроцесса", ПредставлениеПроцесса);
    Индикатор.Вставить("ЛиВыводитьВремя", ЛиВыводитьВремя);
    Индикатор.Вставить("РазрешитьПрерывание", РазрешитьПрерывание);

    Индикатор.Вставить("ДатаНачалаПроцесса", ТекущаяДата());

    Индикатор.Вставить("МинимальныйПериодОбновления", МинимальныйПериодОбновления);
    Индикатор.Вставить("ДатаСледующегоОбновления", Дата('00010101'));

    Индикатор.Вставить("ВнутреннийСчетчик", ВнутреннийСчетчик);
    Если КоличествоОбновлений > 0 Тогда
        Шаг = КоличествоПроходов / КоличествоОбновлений;
    Иначе
        Шаг = 0;
    КонецЕсли;
    Индикатор.Вставить("Шаг", Шаг);
    Индикатор.Вставить("СледующийСчетчик", 0);
    Индикатор.Вставить("Счетчик", 0);
    Возврат Индикатор;

КонецФункции // ЛксПолучитьИндикаторПроцесса()

// Проверяет и обновляет индикатор. Нужно вызывать на каждом проходе индицируемого цикла.
//
// Параметры:
//  Индикатор    – Структура – индикатора, полученная методом ЛксПолучитьИндикаторПроцесса;
//  Счетчик      – Число – внешний счетчик цикла, используется при ВнутреннийСчетчик = Ложь.
//
Процедура ЛксОбработатьИндикатор(Индикатор, Счетчик = 0) Экспорт

    Если Индикатор.ВнутреннийСчетчик Тогда
        Счетчик = Индикатор.Счетчик + 1;
        Индикатор.Счетчик = Счетчик;
    КонецЕсли;
    Если Индикатор.РазрешитьПрерывание Тогда
        ОбработкаПрерыванияПользователя();
    КонецЕсли;
    ОбновитьИндикатор = Истина;
    ТекущаяДата = ТекущаяДата();
    Если Индикатор.МинимальныйПериодОбновления > 0 Тогда
        Если ТекущаяДата >= Индикатор.ДатаСледующегоОбновления Тогда
            Индикатор.ДатаСледующегоОбновления = ТекущаяДата + Индикатор.МинимальныйПериодОбновления;
        Иначе
            ОбновитьИндикатор = Ложь;
        КонецЕсли;
    КонецЕсли;
    Если ОбновитьИндикатор Тогда
        Если Индикатор.Шаг > 0 Тогда
            Если Счетчик >= Индикатор.СледующийСчетчик Тогда
                Индикатор.СледующийСчетчик = Цел(Счетчик + Индикатор.Шаг);
            Иначе
                ОбновитьИндикатор = Ложь;
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;
    Если ОбновитьИндикатор Тогда
        Индикатор.СледующийСчетчик = Цел(Счетчик + Индикатор.Шаг);
        Если Индикатор.ЛиВыводитьВремя Тогда
            ТекущаяДата = ТекущаяДата();
            ПрошлоВремени = ТекущаяДата - Индикатор.ДатаНачалаПроцесса;
            Осталось = ПрошлоВремени * (Индикатор.КоличествоПроходов / Счетчик - 1);
            Часов = Цел(Осталось / 3600);
            Осталось = Осталось - (Часов * 3600);
            Минут = Цел(Осталось / 60);
            Секунд = Цел(Цел(Осталось - (Минут * 60)));
            ОсталосьВремени = Формат(Часов, "ЧЦ=2; ЧН=00; ЧВН=") + ":"
                + Формат(Минут, "ЧЦ=2; ЧН=00; ЧВН=") + ":"
                + Формат(Секунд, "ЧЦ=2; ЧН=00; ЧВН=");
            ТекстОсталось = "Осталось: ~" + ОсталосьВремени;
        Иначе
            ТекстОсталось = "";
        КонецЕсли;
        ТекстСостояния = Индикатор.ПредставлениеПроцесса + " "
            + Формат(Счетчик / Индикатор.КоличествоПроходов * 100, "ЧЦ=3; ЧДЦ=0") + "%  " + ТекстОсталось;
        Если ТипЗнч(Индикатор) = Тип("СтрокаТаблицыЗначений") Тогда
            ТаблицаИндикаторов = Индикатор.Владелец();
            ИндексИндикатора = ТаблицаИндикаторов.Индекс(Индикатор);
            Если ИндексИндикатора > 0 Тогда
                ТекстСостояния = ТаблицаИндикаторов[ИндексИндикатора - 1].ТекстСостояния + " >> " + ТекстСостояния;
            КонецЕсли;
            Индикатор.ТекстСостояния = ТекстСостояния;
        КонецЕсли;
        Состояние(ТекстСостояния);
    КонецЕсли;
    Если Счетчик = Индикатор.КоличествоПроходов Тогда
        Состояние("");
    КонецЕсли;

КонецПроцедуры // ЛксОбработатьИндикатор()

Ключевым моментом в ЛксОбработатьИндикатор() для обновления состояния является требование выполнения
в общем случае двух (любое можно отключить) условий:
- прошло минимальное время с момента последнего обновления
- не превысить заданное общее число обновлений

Вот пример их использования.

КоличествоДанных = 100000;
Индикатор = ЛксПолучитьИндикаторПроцесса(КоличествоДанных, "Проверка данных");
Для Счетчик = 1 По КоличествоДанных Цикл              
 ЛксОбработатьИндикатор(Индикатор, Счетчик);
КонецЦикла;

КоличествоДанных = 100000;
Индикатор = ЛксПолучитьИндикаторПроцесса(КоличествоДанных, "Проверка данных", Истина);
Для Счетчик = 1 По КоличествоДанных Цикл              
 ЛксОбработатьИндикатор(Индикатор);
КонецЦикла;

Пример выполнения можно увидеть на этом скриншоте:

Оригинал статьи: http://infostart.ru/public/57223/.