I am trying to run a lengthy operation with the results being displayed in the window, without blocking the UI thread. What I have is a View
that has a ListView
control, which binds to an ObservableCollection
in my ViewModel
. I also have a few other TextBlock
controls that bind to two arrays. Anything that runs before the lengthy operation within the Task
gets displayed and anything after does not. Here's my code to help you understand what I mean:
Four years:
<TextBlock TextWrapping="Wrap" Grid.Row="1"
Style="{DynamicResource SectionBodyTextStyle}">
<Run Text="{Binding Years[1]}"/>
<Run Text=":"/>
</TextBlock>
<TextBlock TextWrapping="Wrap" Grid.Row="2"
Style="{DynamicResource SectionBodyTextStyle}">
<Run Text="{Binding Years[2]}"/>
<Run Text=":"/>
</TextBlock>
<TextBlock TextWrapping="Wrap" Grid.Row="3"
Style="{DynamicResource SectionBodyTextStyle}">
<Run Text="{Binding Years[3]}"/>
<Run Text=":"/>
</TextBlock>
Four fields that hold the counts for each year.
<TextBlock Text="{Binding YearCounts[0]}" TextWrapping="Wrap"
Grid.Column="1" Margin="10,0,0,0"/>
<TextBlock Text="{Binding YearCounts[1]}" TextWrapping="Wrap"
Grid.Column="1" Grid.Row="1" Margin="10,0,0,0"/>
<TextBlock Text="{Binding YearCounts[2]}" TextWrapping="Wrap"
Grid.Column="1" Grid.Row="2" Margin="10,0,0,0"/>
<TextBlock Text="{Binding YearCounts[3]}" TextWrapping="Wrap"
Grid.Column="1" Grid.Row="3" Margin="10,0,0,0"/>
ListView
to hold the information for each record:
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Report Number"
DisplayMemberBinding="{Binding RepNum}"/>
<GridViewColumn Header="Employee ID"
DisplayMemberBinding="{Binding EmployeeId}"/>
<GridViewColumn Header="Date"
DisplayMemberBinding="{Binding Date}"/>
<GridViewColumn Header="Time"
DisplayMemberBinding="{Binding Time}"/>
</GridView>
</ListView.View>
</ListView>
So far, nothing out of ordinary. However, here's my code related to them.
Properties:
private string[] _years;
public string[] Years
{
get { return _years; }
private set
{
if (_years == value)
{
return;
}
_years = value;
OnPropertyChanged("Years");
}
}
private int[] _yearCounts;
public int[] YearCounts
{
get { return _yearCounts; }
private set
{
if (_yearCounts == value)
{
return;
}
_yearCounts = value;
OnPropertyChanged("YearCounts");
}
}
private ObservableCollection<RecordModel> _missingCollection;
public ObservableCollection<RecordModel> MissingCollection
{
get { return _missingCollection; }
private set
{
if (_missingCollection == value)
{
return;
}
_missingCollection = value;
OnPropertyChanged("MissingCollection");
}
}
Constructor:
public MissingReportsViewModel()
{
YearCounts = new int[4];
Years = new string[4];
Task.Run(() =>
{
SetYears();
MissingCollection = new AccessWorker().GetMissingReports();
SetYearCounts();
});
}
Methods from this ViewModel
:
private void SetYears()
{
for (int i = 0; i < 4; i++)
{
Years[i] = DateTime.Now.AddYears(-i).Year.ToString();
}
}
private void SetYearCounts()
{
for (int i = 0; i < 4; i++)
{
YearCounts[i] = MissingCollection.Where(item => item.RepNum.Substring(0, 4).Equals(Years[i]))
.ToList().Count();
}
}
And there's also method from the access worker, but the code is rather lengthy. It basically connects to an Access database and gets some data.
So, my problem is that if I place any methods before MissingCollection = new AccessWorker().GetMissingReports();
portion within Task.Run()
or outside of it, they will get displayed on the UI. However, if I place anything after that portion, it won't get displayed in the UI. Doesn't matter if the following method is within Task.Run
or not, same result. I've checked and method yield proper values, they just never make it to the UI. I simply don't understand how these two can yield such different results:
// First - Years get displayed.
// Second - After a little while data gets displayed
// Third - Counts never get displayed.
Task.Run(() =>
{
SetYears();
MissingCollection = new AccessWorker().GetMissingReports();
SetYearCounts();
});
// First - After a while, data gets displayed.
// Second - Years and counts do not get displayed.
Task.Run(() =>
{
MissingCollection = new AccessWorker().GetMissingReports();
SetYears();
SetYearCounts();
});
I'm obviously doing something wrong, but I can't figure out what. I've tried invoking into the UI thread, but that didn't do anything.
EDIT:
When I try to invoke an update into the UI thread for my YearsCount
array bindings, I get an odd out of range exception. Here's the code:
private void Initialize()
{
SetYears();
Task.Run(() =>
{
MissingCollection = new AccessWorker().GetMissingReports();
SetYearCounts();
});
}
private void SetYearCounts()
{
for (int i = 0; i < 4; i++)
{
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new Action(() => YearCounts[i] = MissingCollection.Where(
item => item.RepNum.Substring(0, 4).Equals(Years[i])).ToList().Count()));
}
}
When I step through it, it'll go through each index of YearCounts[i], jump out of the SetYearCounts() back to Task.Run(), then jump back into the SetYearsCounts() and use the last i
value, which is 4 in Years[i], which, obviously, throws the out of range exception.
None of this happens when I run the code without Task.Run(). It just freezes for UI until the operation is finished.
If I do this:
private void Initialize()
{
SetYears();
Task.Run(() =>
{
MissingCollection = new AccessWorker().GetMissingReports();
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() => SetYearCounts()));
});
}
...and write out each value as it's being assigned in the Debug window:
Debug.WriteLine(YearCounts[i]);
...it'll write outputs there, but not on the UI window. I am thinking it has to do with the fact that arrays only report on their own change and not the change of the items themselves. I can see that in the debug window when only the initial initializations of the arrays get reported, but no the change to the items. However, what is odd is the fact that anything prior to the observable collection gets updated and anything after doesn't. It has probably to do with the view looking at its data context when observable collection calling a change.
Per request, here's GetMissingReports()
declarative portion:
public ObservableCollection<RecordModel> GetMissingReports() {..}