Самый эффективный метод обновления большого количества объектов (более 1000) в режиме реального времени.

Каким будет наиболее эффективный способ перебора коллекции с большим количеством элементов (1000 и более) и обновления свойств элементов в режиме реального времени? На данный момент моя программа рисует изображение для каждого объекта в коллекции на холсте, используя WriteableBitmap (хотя я не вижу никакой разницы в производительности между этим и простым эллипсом) и перемещает его по экрану. Я пытаюсь сохранить логику как можно более простой на данный момент.

<UserControl.Resources>
    <DataTemplate DataType="{x:Type local:Entity}">
        <Canvas>
            <Image Canvas.Left="{Binding Location.X}"
                   Canvas.Top="{Binding Location.Y}"
                   Width="{Binding Width}"
                   Height="{Binding Height}"
                   Source="{Binding Image}" />
        </Canvas>
    </DataTemplate>
</UserControl.Resources>

<Canvas x:Name="content"
        Width="2000"
        Height="2000"
        Background="LightGreen">
    <ItemsControl Canvas.ZIndex="2" ItemsSource="{Binding Entities}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas IsItemsHost="True" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>

[Magic]
public class Entity : ObservableObject
{
    public Entity()
    {
        Height = 16;
        Width = 16;
        Location = new Vector(Global.rand.Next(800), Global.rand.Next(800));
        Image = Global.LoadBitmap("Resources/Thing1.png");
    }

    public int Height { get; set; }
    public int Width { get; set; }
    public Vector Location { get; set; }
    public WriteableBitmap Image { get; set; }        
}

(В приведенном выше классе Entity атрибут [Magic] реализует INPC для всех моих свойств)

Я пытался использовать System.Timers.Timer, System.Threading.Timer и System.Threading.DispatcherTimer для создания цикла с различными интервалами. Все ведут себя довольно хорошо, пока я не доберусь до 800 объектов в коллекции, а затем они начинают становиться прерывистыми. Я также пытался использовать стандартный цикл foreach и цикл Parallel.ForEach и не заметил разницы между ними. Мой цикл имел более сложную логику, но я максимально упростил его, пока не придумаю, как упростить процесс:

void Timer_Tick(object sender, EventArgs e)
{
    Parallel.ForEach(Entities, entity =>
    {
        entity.Location = new Vector(entity.Location.X + 1, entity.Location.Y);
    });
}

(Кроме того, это не проблема с Canvas: если я нарисую 10 элементов и сделаю 1000 без изображения, он все равно будет прерывистым.)

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


person Jason D    schedule 17.09.2013    source источник


Ответы (1)


Коллекции лучше всего обрабатывать в одном потоке, но есть несколько вариантов:

  • В случае, если какой-либо процесс требует длительного и не полностью потребляющего действия ЦП, вы должны использовать свой метод в вопросе, который представляет собой поток на действие.

  • В случае наличия нескольких коллекций с небольшим временем действия процесса лучше всего использовать только один поток для всех из них.

Также на производительность влияет синхронизация между потоками. Если вы это делаете, и довольно часто, это может замедлить скорость.

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


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


Поток на коллекцию

List<List<Action>> actions = InitializeActions();

foreach(var list in actions)
{
    var listCopy = list; // Mandatory where execution is deferred.

    // Each collection is stared on different thread from thread pool.
    Task.Factory.StartNew(() =>
    {
        foreach(var action in listCopy)
        {
            action();
        }
    }
}

Поток на все

// Single thread executes all the code.
List<List<Action>> actions = InitializeActions();

foreach(var list in actions)
{
    foreach(var action in listCopy)
    {
        action();
    }
}

Поток на действие

List<List<Action>> actions = InitializeActions();

foreach(var list in actions)
{
    foreach(var action in listCopy)
    {
        // Spawn a thread on each action.
        Task.Factory.StartNew(action);
    }
}

Это, конечно, довольно прямолинейно и грубо, но суть вы поняли.

person AgentFire    schedule 17.09.2013
comment
Спасибо за ответ. Я проведу небольшое исследование того, что вы предложили. В контексте моей программы цикл в конечном итоге стал бы чем-то вроде того, чтобы выяснить, куда идти, начать идти туда ... затем, если в пункте назначения, сделать что-то, а затем повторить. Я поиграю с этим и посмотрю, что работает лучше всего. Однако еще раз спасибо. Я думаю, что этот ответ будет хорошим толчком в правильном направлении. - person Jason D; 17.09.2013
comment
Я искал вокруг, и я не могу найти много о том, что вы предложили. Не могли бы вы рассказать немного подробнее о том, как реализовать эти различные методы, или указать мне страницу, которая объясняет это? - person Jason D; 18.09.2013
comment
Спасибо за обновления! Я поиграю с этими методами и посмотрю, что работает лучше всего. Что касается таймера для выполнения в реальном времени, есть ли тот, который вы бы порекомендовали другим для ситуации, подобной той, что я описал в своем исходном посте? - person Jason D; 18.09.2013