Most efficient method to update large number of ob

2019-05-17 21:05发布

问题:

What would be the most efficient way to iterate through a collection with a large number of items (1,000 or more) and update the items' properties in real-time? At the moment, my program draws an image for each object in the collection on a canvas using WriteableBitmap (though I don't see any difference in performance between that an a simple ellipse) and moves it across the screen. I am trying to keep the logic as simple as possible for the time being.

<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; }        
}

(In the Entity class above, the [Magic] attribute implements INPC on all of my properties)

I have tried using System.Timers.Timer, System.Threading.Timer, and System.Threading.DispatcherTimer to create a loop with varying intervals. All behave fairly well until I get to about 800 objects in the collection and then they start to become choppy. I have also tried using a standard foreach loop and a Parallel.ForEach loop and haven't really noticed a difference between the two. My loop had more complex logic, but I have made it as simple as possible until I can figure out how to streamline the process:

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

(Also, this isn't an issue with the Canvas; if I draw 10 items and make 1,000 with no image it still gets choppy.)

What can I do to make my program handle large collections in real-time more efficiently? I'm guessing there's a good bit I'm missing as this is the first time I've ever dealt with anything of this nature. Any advice would be greatly appreciated.

回答1:

Collections are best processed in one-per-thread, but there are some options:

  • In case if any process requires some long and non-fully-consuming CPU action, you are to use your method in the question, which is thread-per-action.

  • In case there are number of collections with small process action time, you might be the best to use only one thread for all of them.

There is also an influence on performance if you do synchronization between your threads. If you do, and pretty often, that might slow the speed down.

In any case, there is always a place to think, and mandatory - for benchmark different methods of processing your action. That latter option will help you understand the multitasked workflow.


In the case of some lightweight arythmetic CPU-bound operations, you are to feel free to use a thread for all of them.


Thread-per-Collection

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();
        }
    }
}

Thread-per-everything

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

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

Thread-per-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);
    }
}

This is of course pretty straightforward and rough, but you get the point.