a couple of points you need to bare in mind when creatnig multithreaded tasks in wpf
firstly unless your background process are directly view related you shouldn't have the thread in your view, leave it in the view model where it belongs, this will remove most of the issues your are having with updates as these will be handled in the binding having no effect on your gui
second if you do need to do something view related, only use the Dispatcher.Invoke for the smallest possible transaction i.e. Textbox.Dispatcher.Invoke(()=>Textbox.Text = "something")
this is because the dispatcher will lock both your thread and the element the dispatcher is attached to while it gets the dispatcher to execute the code in the primary GUI thread of the control
as you said your task is server related then their is not view element to it so yoif you keep it in the view model like it should be then your code should look something like this
public class LoaderViewModel
{
public LoaderViewModel()
{
loader.DataLoaded += Loader_DataLoaded;
}
public Dispatcher Dispatcher {get; set; }
public ObservableCollection<DataViewModel> Values { get; } = new ObservableCollection<DataViewModel>();
ModelLoader loader = new ModelLoader();
public void LoadData()
{
loader.LoadData();
}
private void Loader_DataLoaded(object sender, IEnumerable<DataModel> e)
{
Dispatcher.Invoke(() => Values.Clear());
foreach (var item in e)
{
Dispatcher.Invoke(()=>Values.Add(new DataViewModel( item)));
}
}
}
public class DataViewModel : INotifyPropertyChanged
{
public DataViewModel(DataModel model)
{
Model = model;
Model.ValueLoaded += Model_ValueLoaded;
}
private void Model_ValueLoaded(object sender, EventArgs e)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Text"));
}
public event PropertyChangedEventHandler PropertyChanged;
public DataModel Model { get;private set; }
public string Text
{
get { return Model.Value; }
}
}
public class DataModel
{
public DataModel(int id)
{
ID = id;
}
public int ID { get; set; }
private string _Value = "Loading...";
Task backgroundLoad;
public string Value
{
get {
if (backgroundLoad == null && _Value == "Loading...")//background Load on Demand
{
backgroundLoad = Task.Run(async () =>
{
Value = await Server.GetDataValues(ID);
});
}
return _Value;
}
set
{
if (_Value != value)
{
_Value = value;
ValueLoaded?.Invoke(this, EventArgs.Empty);
}
}
}
public event EventHandler ValueLoaded;
}
public class ModelLoader
{
public void LoadData()
{
loading = Task.Run(async()=>
{
var results = (await Server.GetDataIDs()).Select(i => new DataModel(i));
DataLoaded?.Invoke(this, results);
});
}
private Task loading;
public event EventHandler<IEnumerable<DataModel>> DataLoaded;
}
//simulates a slow responding server
public static class Server
{
private static Random rnd = new Random();
public static async Task<IEnumerable<int>> GetDataIDs()
{
await Task.Delay(5000);
return Enumerable.Range(1, 15);
}
public static async Task<string> GetDataValues(int id)
{
await Task.Delay(rnd.Next(100,6000));
return $"Values {id}";
}
}
the View for the quick demo i knocked up was
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:LoaderViewModel x:Name="viewModel" />
</Window.DataContext>
<DockPanel>
<Button Click="Clear_Click" DockPanel.Dock="Top" >Clear</Button>
<Button Click="Load_Click" DockPanel.Dock="Top">Load Data</Button>
<ListView ItemsSource="{Binding Values}" ScrollViewer.VerticalScrollBarVisibility="Visible" >
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Text}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DockPanel>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
viewModel.Dispatcher = Dispatcher;
}
private void Clear_Click(object sender, RoutedEventArgs e)
{
viewModel.Values.Clear();
}
private void Load_Click(object sender, RoutedEventArgs e)
{
viewModel.LoadData();
}
}
Note: the view is just a quick and dirty example to demo the ViewModel