0

I am trying to implement an indeterminate progress bar into my program. I'm new to threading, but as far as I know one of the best options here is to add an async method, and await the "heavy" function to perform its results. So I wrote this:

public void Window_Loaded(object sender, RoutedEventArgs e)
{
    firstLoad();                         
}

private async void firstLoad()
{
    LW.Title = "Loading...";
    LW.Show();

    filterTextBox.Text = defaultSearch;
    await Task.Run(() => InitializeFilter());          
}

private void InitializeFilter()
{

//Asynchronous??? 
Dispatcher.BeginInvoke(new Action(() => {

//... some lines of code that takes some time to run. 

dataGrid.ItemContainerGenerator.StatusChanged += new EventHandler(closeLoadingWindow);

}));

private void closeLoadingWindow(object sender, EventArgs e)
{
    if (LW != null)
    {
        LW.closable = true;
        LW.Close();
    }
}

firstLoad runs when the window is loaded, showing an indeterminate LW loadingWindow, and running the InitializeFilter() method (the heavy one). Finally, when the grid is populated and loaded, an event fires, allowing the LW window to be closed and closing it (if I didn't make it unclosable, a funny user could just close it clicking or using F4, which is not nice).

The system is working properly and everything works as expected regarding time frames, but the loading bar is frozen, not showing progress. The same LW bar works in the MainWindow with a similar set up What am I missing? Thanks in advance!

6
  • your await statement is instructing window_Loaded to stop processing and wait for firstLoad to finish, i would suggest moving the async to the event handler (ie public async void Window_Loaded), or doing fire and forget on the task Commented May 3, 2016 at 16:41
  • Anything executed via Dispatcher.BeginInvoke will be executed on the main thread, so it will block the UI Commented May 3, 2016 at 16:42
  • Gusman is correct you need to reduce the Dispatcher.Invoke to just the part that thread dependent probably altering the collection Commented May 3, 2016 at 16:45
  • That's what I tried initially, and this is the error I got: "The calling thread cannot access this object because a different thread owns it." Commented May 3, 2016 at 16:48
  • You're trying to acces UI controls from the async task, you need to rationalize the UI access, first retrieve all the data you need from the UI on the main thread, on the async task then process all what you need, and finally update the UI from the main thread Commented May 3, 2016 at 16:49

2 Answers 2

2

as far as I know one of the best options here is to add an async method, and await the "heavy" function to perform its results

The best option is to use Task.Run to move the heavy processing to the thread pool, and use await to retrieve its results.

The code as it currently stands uses Task.Run to move to the thread pool and then immediately turns around and uses Dispatcher to move back to the UI thread before doing the heavy processing. Thus, it's blocking the UI thread.

what this particular DataGrid displays is a CollectionView, which is not thread-safe.

Right, you can't update data-bound objects from a thread pool thread.

The best solution is to separate the heavy processing from the UI updates, something like this:

public async void Window_Loaded(object sender, RoutedEventArgs e)
{
  await firstLoadAsync();
}

private List<FilterType> InitializeFilter()
{
  //... some lines of code that takes some time to run. 
}

private async Task firstLoadAsync()
{
  LW.Title = "Loading...";
  LW.Show();

  filterTextBox.Text = defaultSearch;
  var filterData = await Task.Run(() => InitializeFilter()); // Get the plain data on a background thread
  myCollectionView = new CollectionView(filterData); // Update the UI
  if (LW != null)
  {
    LW.closable = true;
    LW.Close();
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Hi Stephen, I followed your advice and everything's working as expected, but weirdly enough the LW is standing still yet! If I force it to stay open the animation starts as soon as the dataGrid is loaded. Being honest I am quite clueless now. On the bright side, taking your approach and rewriting what I had has apparently improved dataGrid loading times anyway.
0

do not use your dispatcher. Microsoft had the foresight to use it's magic (SynchronizationContext) to be able to update the UI thread in a method that is being executed in an async context. This is demonstrated in their async/await example found here

while under previous/other circumstances, you would have to either marshal back to the main (UI) thread to update the UI thread, or wait until completed and retrieve the results from objects who share state. Since you are using async/await then you should be fine to not use the dispatcher, and update the UI directly.

1 Comment

While dealing with this issue I just stumbled upon a part of the problem that I hadn't considered: what this particular DataGrid displays is a CollectionView, which is not thread-safe. That's the reason why Gusman and MikeT's advice didn't work in this particular case. Using the dispatcher, there's no exception and everything runs, but UI is not updating. Not using it means getting the exception thrown. Apart from your example, I'm taking a look at this. I'm definitely getting the hang of it, but I'm not quite there yet. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.