0

I'm trying to fill devexpress GridControl in background (it's not quick process). I do it like this:

...
CreateGrid();
ShowMessageInsteadOfGridControl;
...
FillGrid(dataGrid, other UI params);
...

Write data in Grid:

private void FillGrid(GridControl data, ...);
{
   Task.Factory.StartNew(() =>
                                 {
                                      Application.Current.Dispatcher.Invoke(new Action(() => FillData(gridControl,UIparamns)),
                                                        DispatcherPriority.Background);
                                  }).ContinueWith(c => HideUserMessage(UIparamns));
}

When I call FillData, it causes UI freezing. I can't use usual Task, because Grid filled from UI and I have "The calling thread cannot access this object".

How to make such dataposting process in background without freezing UI?

Peter
  • 15
  • 4

2 Answers2

0

Generally it is dangerous to use Task.Factory.StartNew(...) when working with code related to the UI thread, because the first time it will examine the TaskScheduler, find that there isn't one, and it will use a thread pool TaskScheduler. When the thread is done computing using a function called, say, Compute(3), it returns but here it marshals back to the UI thread, then will update the UI thread with the results so far.

The second and third times it calls Compute, since the SynchronizationContext has been marshaled back to the UI, it will then run on a UI thread, thus blocking your UI.

private void Form1_Load(object sender, EventArgs e)
{
    Task.Factory.StartNew(A);
}

private static void A() { }

private void Form1_Load(object sender, EventArgs e)
{
    Compute(3);
}

private void Compute(int counter)
{
    // If we're done computing, just return.
    if (counter == 0)
        return;

    var ui = TaskScheduler.FromCurrentSynchronizationContext();
    Task.Factory.StartNew(() => A(counter))
        .ContinueWith(t =>
        {
            Text = t.Result.ToString(); // Update UI with results.

            // Continue working.
            Compute(counter - 1);
        }, ui);
}

private int A(int value)
{
    return value; // CPU-intensive work.
}

To avoid this potential problem, instead use Task.Run(() => A());

For more details, please see Stephen Cleary's article at https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html and/or What is the difference between Task.Run() and Task.Factory.StartNew()

Here is a way that I've found to load data for a datagrid in the background while not blocking your UI.

First, create a lock object, and enable collection synchronization, then actually load the data on a background thread using Task.Run():

    private readonly object _myDataLock = new object();

    private FastObservableCollection<My20FieldsDataRecord> MyList = new FastObservableCollection<My20FieldsDataRecord>();
    private CollectionViewSource MyListCollectionView = new CollectionViewSource();

    public MyViewModelConstructor() : base()
    {
        // Other ctor code 
        // ...
        // assign the data source of the collection views
        MyListCollectionView.Source = MyList;               

        // Setup synchronization
        BindingOperations.EnableCollectionSynchronization(MyList, _myDataLock);
    }

    private async void LoadMyList()
    {
                // load the list
                await Task.Run(async () =>
                    {
                        MyList.ReplaceAll(await MyRepository.LoadMyList());
                    }
                );      
    }

Then in your repository you could write:

    public virtual async Task<IList<My20FieldsDataRecord>> LoadMyList()
    {
        var results = await this.DataContext.TwentyFieldDataRecords
           .OrderByDescending(x => x.MyDate).ToListAsync().ConfigureAwait(false);

        return results;
    }   

Then you could bind in your associated view like this:

<controls:DataGrid Name="MyListDataGrid" Grid.Row="1"
                   ....
                   ItemsSource="{Binding MyListCollectionView.View}" 
                   ... >

For details, please see:

user8128167
  • 6,929
  • 6
  • 66
  • 79
0

The dispatcher invoke call puts everything back on the UI thread, you need to split your operation into parts which can be done in the background and those which really need to be done on the UI-thread, like adding an item, only pass those operations to the dispatcher.

(DispatcherPriority.Background just means that other items, with higher priority, will be executed first, this has nothing to do with background threads, every call to the dispatcher of the UI-thread leads to that operation being executed on said UI-thread sooner or later)

H.B.
  • 166,899
  • 29
  • 327
  • 400