简体   繁体   中英

Fill List<object> in a new thread and get it back to the UI Thread

I got following function generating a List of LinesVisual3D objects. I need to do this in a new task, because it's very time consuming.

public async Task<List<LinesVisual3D>> Create2dGcodeLayerModelListAsync(IProgress<int> prog = null)
    {
        var list = new List<LinesVisual3D>();
        try
        {
            await Task.Factory.StartNew(delegate ()
            {
                var temp = new List<LinesVisual3D>();
                int i = 0;
                foreach (List<GCodeCommand> commands in Model.Commands)
                {
                    var line = drawLayer(i, 0, Model.Commands[i].Count, false);
                    /*
                    if (DispatcherObject.Thread != Thread.CurrentThread)
                        DispatcherObject.BeginInvoke(new Action(() => list.Add(drawLayer(i, 0, Model.Commands[i].Count, false))));

                    */
                    temp.Add(drawLayer(i, 0, Model.Commands[i].Count, false));

                    if (prog != null)
                    {
                        float test = (((float)i / Model.Commands.Count) * 100f);
                        if (i < Model.Commands.Count - 1)
                            prog.Report(Convert.ToInt32(test));
                        else
                            prog.Report(100);
                    }
                    i++;
                }

                list = new List<LinesVisual3D>(temp);
            });
            LayerModelGenerated = true;
            return list;

        }
        catch (Exception exc)
        {
            return list;
        }
    }

在此处输入图片说明

It actually works fine, however I do not get the list out of the background thread. When I access the list in the UI (different thread), I'll get this result:

在此处输入图片说明

I know that the problem is, that the list was filled / generated in a different thread (in this case, ThreadId = 3). However the UI is running in ThreadId = 1

I already tried to invoke directly after the loop has finished.

//DispatcherObject = Dispatcher.CurrentDispatcher;
                if (DispatcherObject.Thread != Thread.CurrentThread)
                    DispatcherObject.BeginInvoke(new Action(() => list = new List<LinesVisual3D>(temp)));
                else
                    list = new List<LinesVisual3D>(temp);

I also tried to invoke while adding to the list.

if (DispatcherObject.Thread != Thread.CurrentThread)
                        DispatcherObject.BeginInvoke(new Action(() => list.Add(drawLayer(i, 0, Model.Commands[i].Count, false))));

The result always was the same.

EDIT1: Tried with single instance instead of list.

public async Task<LinesVisual3D> Create2dGcodeLayerAsync(IProgress<int> prog = null)
    {
        var temp = new LinesVisual3D();
        try
        {

            await Task.Factory.StartNew(delegate ()
            {
                if (DispatcherObject.Thread != Thread.CurrentThread)
                    DispatcherObject.BeginInvoke(new Action(() => temp = drawLayer(3, 0, Model.Commands[3].Count, false)));
                //temp = drawLayer(3, 0, Model.Commands[3].Count, false);
            });
            LayerModelGenerated = true;
            return temp;

        }
        catch (Exception exc)
        {
            return temp;
        }
    }

This seems to work. I guess becuase the temp object is generated outside the new task, however the object line in the task?

EDIT2: This function works, however freezes the UI...

public async Task<List<LinesVisual3D>> Create2dGcodeLayerModelListAsync(IProgress<int> prog = null)
    {
        var list = new List<LinesVisual3D>();
        var line = new LinesVisual3D();
        try
        {
            await Task.Factory.StartNew(delegate ()
            {
                var temp = new List<LinesVisual3D>();
                int i = 0;
                foreach (List<GCodeCommand> commands in Model.Commands)
                {
                    // Freezes the UI...
                    if (DispatcherObject.Thread != Thread.CurrentThread)
                    {
                        DispatcherObject.Invoke(new Action(() =>
                        {
                            line = drawLayer(i, 0, Model.Commands[i].Count, false);
                            list.Add(line);
                        }));
                    }

                    if (prog != null)
                    {
                        float test = (((float)i / Model.Commands.Count) * 100f);
                        if (i < Model.Commands.Count - 1)
                            prog.Report(Convert.ToInt32(test));
                        else
                            prog.Report(100);
                    }
                    i++;
                }
            });
            LayerModelGenerated = true;
            return list;

        }
        catch (Exception exc)
        {
            return list;
        }
    }

Either it works and freezes the UI, or it leaves the list in the old thread and doesn't freeze the UI :(

Solution:

Instead of creating the LinesVisual3D object in the loop, I just create a List of Point3D and create a new LinesVisual3D at the Invoke .

public async Task<List<LinesVisual3D>> Create2dGcodeLayerModelListAsync(IProgress<int> prog = null)
    {
        var list = new List<LinesVisual3D>();
        var line = new LinesVisual3D();
        try
        {
            await Task.Factory.StartNew(delegate () 
            {
                var temp = new List<LinesVisual3D>();
                int i = 0;
                foreach (List<GCodeCommand> commands in Model.Commands)
                {
                    var pointsPerLayer = getLayerPointsCollection(i, 0, Model.Commands[i].Count, false);
                    if (DispatcherObject.Thread != Thread.CurrentThread)
                    {
                        DispatcherObject.Invoke(new Action(() =>
                        {
                            line = new LinesVisual3D() { Points = new Point3DCollection(pointsPerLayer)};
                            list.Add(line);
                        }));
                    }

                    if (prog != null)
                    {
                        float test = (((float)i / Model.Commands.Count) * 100f);
                        if (i < Model.Commands.Count - 1)
                            prog.Report(Convert.ToInt32(test));
                        else
                            prog.Report(100);
                    }
                    i++;
                }
            });
            LayerModelGenerated = true;
            return list;

        }
        catch (Exception exc)
        {
            return list;
        }
    }

Points:

private List<Point3D> getLayerPointsCollection(int layerNumber, int fromProgress, int toProgress, bool isNextLayer)
    {
        ...
    }

I do not get the list out of the background thread. When I access the list in the UI (different thread), I'll get this result:

You get the list out of the background thread, what causes problems is accessing the properties of the individual list items.

Most likely, those items are ui objects themselves and have to be created on the ui thread. So you have to dispatch each new back to the ui thread, which essentially leaves adding them to the list as only job for the background task, which will hardly be cpu-bound, so just drop the background thread.

at a guess I'd say the initial call to Create2dGcodeLayerModelListAsync returns the first empty instance of list, when the inner thread starts it also creates a new instance of list in fact two new instances as the last line initialises a new instance with temp, the population of these instances are never returned from Create2dGcodeLayerModelListAsync.

try replace

 temp.Add(drawLayer(i, 0, Model.Commands[i].Count, false));

with

list.Add(drawLayer(i, 0, Model.Commands[i].Count, false));

and delete

list = new List<LinesVisual3D>(temp);

As the call to Create2dGcodeLayerModelListAsync is async (await ..) it will spin up a worker thread anyway the inner thread is redundant and waists time spinning up yet another thread. but start by getting rid of the news first.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM