This is a migrated thread and some comments may be shown as answers.

"LayoutQueue" error?

8 Answers 35 Views
GridView
This is a migrated thread and some comments may be shown as answers.
Chris Kirkman
Top achievements
Rank 1
Chris Kirkman asked on 27 Feb 2018, 03:28 PM

I have a strange error. See attached.  I have a form with a GridView.  The user clicks a button which in turn causes the GridView to bind to a datasource that I call a BindingList of "ComparisonScanViewModel".  For this example the contents of this class are not relevant.  The grid immediately binds correctly.  In a background thread a value within each instance named "Gamma" is calculated.  The result is that the grid is filled with rows representing all the items in the list and as each "Gamma" is calculated the grid reflects this by updating the "Gamma (Passing %)" column.

// user wants to compare now
private async void CompareCommandBarButton_Click(object sender, EventArgs e)
{
    _projectViewModel = ReferenceProjectDropDownList.SelectedValue as ProjectViewModel;
 
    UpdateWaitingBar(ElementVisibility.Visible, "Finding matches / Calculating Passing Percentage...");
    if (null != _projectViewModel)
        await GetScans();
    UpdateWaitingBar(ElementVisibility.Collapsed);
}

 

The "await GetScans()" function does all the heavy lifting.  This function can be stopped elsewhere (when a user wants to close the form before this function has completed), which is why the "private ManualResetEvent _reset = null;" exists in the class.  The important part of this function is the line "scanModel.FindMatches(_snc.Measuring.GetProject(_projectViewModel.Id));".  This is where my "Gamma" value is calculated and the grid is updated to reflect the result.  See below...

/// <summary>
/// Calculate the gammas for the scans in the project
/// </summary>
private async Task GetScans()
{
    _reset?.Set();
    _task?.Wait();
 
    if (null != _projectViewModel)
    {
        // reference project name
        _referenceProjectName = _projectViewModel.Name;
 
        // what is the current analysis parameter protocol
        _protocol = (AnalysisProtocolType)_snc.Administration.GetPreference(
            PreferenceType.AnalysisProtocolType).IntValue;
 
        _model = new ComparisonScansViewModel(_snc, _protocol, _scans);
        if (null != _model)
            BatchComparisonGrid.DataSource = _model.Scans;
 
        UpdateWaitingBar(ElementVisibility.Visible, "Finding matches / Calculating Passing Percentage...");
 
        // try to calculate match/gamma
        _task = Task.Run(() =>
        {
            _reset = new ManualResetEvent(false);
 
            try
            {
                foreach (ComparisonScanViewModel scanModel in _model.Scans)
                {
                    if (_reset.WaitOne(0)) break;
                    scanModel.FindMatches(_snc.Measuring.GetProject(_projectViewModel.Id));
                }
            }
            catch (Exception)
            {
 
            }
            finally
            {
                _reset?.Close();
                _reset?.Dispose();
                _reset = null;
            }
        });
        await _task;
    }
 
    UpdateWaitingBar(ElementVisibility.Collapsed);
}

 

This is what the UI looks like as "Gamma" is calculated within my model and it's value is reflected in the grid.  *Notice that there are rows with no "Gamma" and as "Gamma" is calculated it is shown on that column (this is exactly how it supposed to work).  Occasionally I get the error attached, the "LayoutQueue" error while the model is being updated and then reflected in to the grid.  Any ideas why this is happening?

 

 

 

8 Answers, 1 is accepted

Sort by
0
Dimitar
Telerik team
answered on 28 Feb 2018, 10:46 AM
Hi Chris,

You cannot update controls from other than the UI thread. Detailed information is available here: Combining Multithreading with RadGridView (there are some useful links at the end of the article as well).

I want to share another approach that can be useful for such cases:
public partial class RadForm1 : Telerik.WinControls.UI.RadForm
{
    List<Data> data = new List<Data>();
    public Action<List<Data>> Callback { get; private set; }
    public RadForm1()
    {
        InitializeComponent();
 
        this.Callback = new Action<List<Data>>(query =>
        {
            this.data = query;
            this.radWaitingBar1.StopWaiting();
            this.radGridView1.DataSource = data;
 
        });
 
 
    }
    protected async void ExecuteQueryAsync<T>(Task<T> task, Action<T> callback)
    {
        var result = await task;
        callback(result);
    }
 
    private  List<Data> GetScans()
    {
        var result = new List<Data>();
 
        for (int i = 0; i < 100; i++)
        {
            result.Add(new Data() { ID = i, Text = "Row" + i });
        }
        Thread.Sleep(5000);
 
        return result;
    }
    private void radButton1_Click(object sender, EventArgs e)
    {
        radWaitingBar1.StartWaiting();
        ExecuteQueryAsync<List<Data>>( Task.Run(() => GetScans()), this.Callback);
    }
}
public class Data
{
    public int ID { get; set; }
 
    public string Text { get; set; }
}

I hope this will be useful. Let me know if you have additional questions.

Regards,
Dimitar
Progress Telerik
Try our brand new, jQuery-free Angular components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
0
Chris Kirkman
Top achievements
Rank 1
answered on 28 Feb 2018, 02:57 PM

I might not have been clear.  My desired functionality IS NOT to fill a list of data and then bind it to the grid when it is complete. 

  1. My goal is to fill the grid with data view a BindingList<mymodel>.  This works file as I need it to be exceptionally fast. 
  2. Next, after the grid has been filled with data, calculate a value (only 1 value - a number) within each row (a view model per row).  I want to see that result update real time in each row as it is calculated.
  3. I understand the concept of not being able to update the UI from a background thread, that's not my issue.  My issue is that a change to the view model (that has already been bound) shouldn't result in the grid throwing an exception.

I've changed my code to be easier to follow.  "FindComparisonMatches" is my background thread function.  Is it really necessary to  update a view model property that is already using 2 way binding on a datasource that was already set on the UI thread?  Please see below...

// user wants to compare now
private async void CompareCommandBarButton_Click(object sender, EventArgs e)
{
    _projectViewModel = ReferenceProjectDropDownList.SelectedValue as ProjectViewModel;
 
    // tell user we're calculating
    UpdateWaitingBar(ElementVisibility.Visible, "Finding matches / Calculating Passing Percentage...");
 
    if (null != _projectViewModel)
    {
        #region Set the grid datasource
        // reference project name
        _referenceProjectName = _projectViewModel.Name;
 
        // what is the current analysis parameter protocol
        _protocol = (AnalysisProtocolType)_snc.Administration.GetPreference(
            PreferenceType.AnalysisProtocolType).IntValue;
 
        // create a new instance of the scans model and bind to the comparison grid
        _model = new ComparisonScansViewModel(_snc, _protocol, _scans);
        if (null != _model)
            BatchComparisonGrid.DataSource = _model.Scans;
        #endregion
 
        // find matches and comparison results
        await FindComparisonMatches();
    }
 
    // tell user we're done
    UpdateWaitingBar(ElementVisibility.Collapsed);
}
 
/// <summary>
/// Calculate the gammas for the scans in the project
/// </summary>
private async Task FindComparisonMatches()
{
    _reset?.Set();
    _task?.Wait();
 
    // try to calculate match/gamma
    _task = Task.Run(() =>
    {
        _reset = new ManualResetEvent(false);
 
        try
        {
            foreach (ComparisonScanViewModel scanModel in _model.Scans)
            {
                if (_reset.WaitOne(0)) break;
                scanModel.FindMatches(_snc.Measuring.GetProject(_projectViewModel.Id));
            }
        }
        catch (Exception) { }
        finally
        {
            _reset?.Close();
            _reset?.Dispose();
            _reset = null;
        }
    });
    await _task;
}
0
Chris Kirkman
Top achievements
Rank 1
answered on 28 Feb 2018, 03:06 PM

As a follow up.  The only thing that "scanModel.FindMatches()" does is to update a value in EACH view model...nothing else.  This is why I'm doing it in a loop.  Maybe the name scanModel.FindMatches() is misleading.  It's not getting an entirely new set of data that is being bound to the grid.  It is only updating ONE value inside each row of the grid.  Remember, each row in my data grid is bound to my "ComparisonScanViewModel".  It could just as easily be called scanModel.UpdateSomeValue().

This value had already been bound to the data grid when the data grid datasource was set on the UI thread.  This is simply updating the value within the model...in a background thread.

0
Hristo
Telerik team
answered on 01 Mar 2018, 03:29 PM
Hello Chris,

If I understand correctly your local setup you are updating the view model on a thread which is different than the UI thread. Updating a property in your view model on this background thread would raise the ListChanged event in the BindingList and since the grid is bound to that list it will try to update. The grid is created on the UI thread and the update in the view model object has happened on the background thread and this at some point will lead to an exception.

You can also check the following threads discussing a similar setup:
I hope this helps. Let me know if you need further assistance.

Regards,
Hristo
Progress Telerik
Try our brand new, jQuery-free Angular components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
0
Chris Kirkman
Top achievements
Rank 1
answered on 01 Mar 2018, 03:58 PM

Your first example is simplistic and indicates that it is doing exactly what I'm doing. 

Before executing on the background worker's thread the GridView.Datasource is set on the UI thread.  This is the same thing I'm doing.  And then, in the background thread the model is being updated.  This also is the same thing I'm doing.  An Inovke is being called, something that I'm not doing; however, what is stopping the "ListChanged" event from occurring when the values in the model are updated?  You're still updating the model in the background thread.  Am I missing something?

For clarity, my GridView updates as soon as I update my value in my model in the background thread.  I don't get an exception when this happens.  This "LayoutQueue" exception that I initially reported is very inconsistent and not common; however, it does happen sometimes.

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    while (m_Continue == true)
    {
        m_List[0].Id = m_List[0].Id + 1;
        m_List[0].Name = "Name " + m_List[0].Id;
        m_List[1].Id = m_List[1].Id + 1;
        m_List[1].Name = "Name " + m_List[1].Id;
        m_List[2].Id = m_List[2].Id + 1;
        m_List[2].Name = "Name " + m_List[2].Id;
        Invoke(new ResetBindingCallback(ResetBinding));
        System.Threading.Thread.Sleep(1000);
    }
}

 

If it's critical that I don't want changes in the model to result in the "ListChanged" event from being raised how is this accomplished if changes to my model happen on a background thread?  Is there a property on the GridView that says to ignore this event when it is raised?

 

0
Hristo
Telerik team
answered on 01 Mar 2018, 04:40 PM
Hi Chris,

Such errors might not always be thrown but at some point, if the operation is more time consuming a similar error will be raised which not always will be with the same call stack. As you have mentioned in your first post the LayoutQueue error gets raised occasionally while you are updating the model from the separate thread. There is no property in the grid saying to ignore changes in the data source object. You can try invoking the grid using Control.BeginInvoke whenever you are about the make a change in the data object: 
public partial class Form1 : Telerik.WinControls.UI.RadForm
{
    private BindingList<Model> data = new BindingList<Model>();
 
    public Form1()
    {
        InitializeComponent();
 
        for (int i = 0; i < 2000; i++)
        {
            this.data.Add(new Model()
            {
                FirstName = "James " + i,
                LastName = "Wright " + i,
                Gender = "male"
            });
        }
         
        this.radGridView1.DataSource = this.data;
        this.radGridView1.AutoSizeColumnsMode = GridViewAutoSizeColumnsMode.Fill;
        this.button1.Click += radButton1_Click;
    }
 
    private void radButton1_Click(object sender, EventArgs e)
    {
        Thread backgroundThread = new Thread(
        new ThreadStart(() =>
        {
            for (int n = 0; n < 100; n++)
            {
                this.radGridView1.BeginInvoke(
                    new Action(() =>
                    {
                        Model m = this.data[n];
                        m.FirstName = " Updated from thread";
                    }));
 
                Thread.Sleep(25);
            }
 
            this.radGridView1.BeginInvoke(
                    new Action(() =>
                    {
                        this.radGridView1.MasterTemplate.Refresh();
                    }));
 
            MessageBox.Show("Thread completed!");
        }));
 
        backgroundThread.Start();
    }
}
 
public class Model
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Gender { get; set; }
}

I hope this information was useful.

Regards,
Hristo
Progress Telerik
Try our brand new, jQuery-free Angular components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
0
Chris Kirkman
Top achievements
Rank 1
answered on 01 Mar 2018, 07:49 PM

The biggest bummer with this solution is that it locks up the entire UI until the action completes, which defeats the purpose of a background thread.  :(

Anyway, I just changed my model to return a result when calling "model.FindMatches()" instead of before where it was a void.  Then I can invoke a new action and call a new method on the model named "model.Update(newobject)".  It wasn't ideal since I had to retrofit a bit of code to ensure this worked, but at least it was a solution that should ensure I don't see this problem in the future.  Thanks for your help.

foreach (ComparisonScanViewModel scanModel in _model.Scans)
{
    if (_reset.WaitOne(0)) break;
 
    ScanMatchResult result = scanModel.FindMatches(_snc.Measuring.GetProject(_projectViewModel.Id));
 
    // update ui
    Invoke(new Action(() => scanModel.Update(result)));                       
}
0
Accepted
Hristo
Telerik team
answered on 02 Mar 2018, 04:06 PM
Hello Chris,

Thank you for the update. I am glad that you have managed to find a working solution.

In case you need further assistance, please let me know.

Regards,
Hristo
Progress Telerik
Try our brand new, jQuery-free Angular components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
Tags
GridView
Asked by
Chris Kirkman
Top achievements
Rank 1
Answers by
Dimitar
Telerik team
Chris Kirkman
Top achievements
Rank 1
Hristo
Telerik team
Share this question
or