Смешивание предварительного расчета с ленивой загрузкой

У меня есть приложение, содержащее кучу разделов, каждый из которых содержит данные, для расчета которых требуется значительное время. Я вижу несколько подходов к этому:

  1. Простое решение 1: Рассчитать данные во всех разделах при запуске приложения. Это означает, что запуск приложения будет медленным.

  2. Простое решение 2: вычисляйте данные в каждом разделе по мере их загрузки пользователем. Это означает, что загрузка каждого раздела будет медленной (что плохо, потому что каждый раздел уже должен выполнять значительные вычисления рендеринга, поэтому они уже немного медленные.

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

  4. Итак, это подводит меня к решению: при запуске приложения начать вычисление данных раздела в фоновом потоке, но когда пользователь загружает раздел, приостановить фоновый поток и начать расчет для раздела, запрошенного пользователем. Если только расчет уже не запущен, в этом случае вы даете ему завершиться.

Я не уверен, как реализовать решение 4. Я думаю, что самое простое решение, вероятно, состоит в том, чтобы использовать очередь с приоритетами и просто повышать приоритет задачи при ее загрузке, но я не уверен, как реализовать это на С#. Это разумный подход? Какие хорошие библиотеки существуют для приоритетных очередей в C#?


person kerkeslager    schedule 30.11.2012    source источник


Ответы (2)


Итак, мы начнем с массива (или любой другой структуры данных, если хотите) из Task<Section> объектов.

private Task<Section>[] sections = new Task<Section>[5];

Вы должны убедиться, что коллекция создана и действительно определяет задачи в самом начале вашего приложения (а не в фоновом режиме).

for (int i = 0; i < sections.Length; i++)
{
    int sectionNumber = i; //copy for closure
    Task<Section> next = new Task<Section>(() => CreateSection(sectionNumber));
    //note the task isn't started.
    sections[i] = next;
}

Затем вы можете запустить фоновую задачу, чтобы фактически обрабатывать задачи по одной:

Task.Run(() =>
{
    for (int i = 0; i < sections.Length; i++)
    {
        EnsureStarted(sections[i]);
        sections[i].Wait();
    }
});

Вы можете использовать Parallel.For, если хотите, чтобы несколько потоков работали над элементами.

Этот метод использует этот вспомогательный метод:

public void EnsureStarted(Task task)
{
    if (task.Status == TaskStatus.Created)
    {
        task.Start();
    }
}

Затем, чтобы получить готовый раздел (запустив его при необходимости):

public Section GetFinishedSection(int sectionNumber)
{
    EnsureStarted(sections[sectionNumber]);
    return sections[sectionNumber].Result;
}

Если вам нужна неблокирующая версия этого (возможно, await), вы можете использовать это:

public Task<Section> EnsureSectionComplete(int sectionNumber)
{
    EnsureStarted(sections[sectionNumber]);
    return sections[sectionNumber];
}
person Servy    schedule 30.11.2012

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

Вот пример установки приоритета из MSDN.

person NSjonas    schedule 30.11.2012
comment
вместо того, чтобы удалять другую задачу из очереди, очереди может быть проще просто проверить, завершен ли уже элемент, над которым она собирается начать работу. Тот же результат, но проще реализовать. - person Servy; 30.11.2012