I have problems, updating a WPF ListView Bound to a an ObservableCollection<T> within an Task Thread using (Task Parallel Library)
I have an Small Tool reading Edifact Files, and displaying an Count of each Segment (first three letters of a Line).
The contained Segments with their Counts are displayed in an Listbox.
Wenn I initially Load a File all works fine, and I see the GUI Counting up the Segments.
My Programm allowed switching to another File, If I do that (using exactly the same Code) it failes with the following Exception.
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
Here is the Code that fails
public class SegementLineCollection : ObservableCollection<SegmentLine>
{
public void IncrementCount(string Segment)
{
Segment = Segment.ToUpper();
SegmentLine line;
if (this.Select(x => x.SegmentName).Contains(Segment))
{
line = this.Where(x => x.SegmentName == Segment).FirstOrDefault();
}
else
{
line = new SegmentLine();
line.SegmentName = Segment;
this.Add(line); // <--- At this point comes the Exception
}
line.Count++;
}
}
Here is the TPL Call I use:
private string _FileName;
public string FileName
{
get
{
return _FileName;
}
set
{
_FileName = value;
OnPropertyChanged("FileName");
if (!String.IsNullOrEmpty(value))
new Task(() => StartFile()).Start();
}
}
Does anyone have any hit for me?
------------ E D I T ------------------
The provided Answers using TaskScheduler.FromCurrentSynchronizationContext() or Dispatcher did not do the Trick!
Is it possible that changing the Binding when loading the new does the trick.
Here is the Binding I use, the Reader Onject is Switched in the ViewModel, and the GUI is Notfied with INotifyPropertyChanged implementation
Use a dispatcher to access the collection:
if (Dispatcher.CurrentDispatcher.CheckAccess())
this.Add(...)
else
Dispatcher.CurrentDispatcher.Invoke(() => this.Add(...));
You need to call IncrementCount on the GUI thread.
With TPL you can use TaskScheduler.FromCurrentSynchroniztionContext() in your task or continuation.
var task = new Task<string>(() => { return "segment"; })
var task2 = task.ContinueWith(t => IncrementCount(t.Result),
TaskScheduler.FromCurrentSynchroniztionContext());
task.Start();
As you are acting on different thread you need to use Dispatcher.BeginInvoke to run an updates on a collection bound to UI
I have a solution for this kind of problems in the following blog..
http://bathinenivenkatesh.blogspot.co.uk/2011/07/wpf-build-more-responsive-ui.html
it got detailed explanation and code snippets...
Related
I have a background thread with a long running task.
The background thread searches for files according to given filters.
The task might be running very long so I do not want to wait for task completion in order to show some results.
Additionally, I do not want to lock up my UI thread in order to check if there are new results from the background task.
I would rather want to notify my main thread: "Hey, there is a new search result added".
Kind of like windows explorer, showing search results while the search is still ongoing:
foreach (FileInfo itemToFilter in unfilteredSearchResults)
{
if (extension == ".wav" || extension == ".flac" || extension == ".mp3")
{
// read audio information such as tags etc. Might take some time per file
WaveFileLib.WaveFile.WAVEFile audioItem = new WaveFileLib.WaveFile.WAVEFile(audioFile.FullName);
// Compare audio information such as tags, title, license, date, length etc to filter
if (Filter.AudioItemMatchesFilter(ref audioItem))
{
lock (threadLock)
{
// add search result to list of filtered audio files
this.AudioItemList.Add(audioItem);
}
// notify main thread in order to refresh ui (if applicable)
ParentView.AudioItemAdded();
}
}
}
the main thread can then generate a view from the newly added item.
Previously, this was quite easy with BeginInvoke but with .net core this possibility seems gone.
What are my alternatives / options to notify main thread about updated search results?
As supposed by Panagiotis Kanavos
public class AudioFileSearcher
{
// task caller
public AudioFileSearcher(string searchPath, bool includeSubFolders, Views.SoundListView.SoundList parentView)
{
this.progress1 = new Progress<int>(
{
backgroundWorker1_ProgressChanged();
});
Task.Run(async () => await FindAudioFiles(progress1));
}
// reference to be updated by task and read by event Handler
private Progress<int> progress1 { get; set; }
// do something on task progress
void backgroundWorker1_ProgressChanged()
{
// Update UI on main thread or whatever
}
// Task
public async Task FindAudioFiles(IProgress<int> progress)
{
foreach(AudioItem itemToProcess in allFoundaudioItems)
{
// do long processing in background task
// a new element has been generated, update UI
if (progress != null) progress.Report(0); // ~fire event
}
}
}
works like a charm, items are beeing loaded
You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:
IObservable<WaveFileLib.WaveFile.WAVEFile> query =
from itemToFilter in unfilteredSearchResults.ToObservable()
where extension == ".wav" || extension == ".flac" || extension == ".mp3"
from audioItem in Observable.Start(() => new WaveFileLib.WaveFile.WAVEFile(audioFile.FullName))
from x in Observable.Start(() =>
{
var a = audioItem;
var flag = Filter.AudioItemMatchesFilter(ref a);
return new { flag, audioItem = a };
})
where x.flag
select x.audioItem;
IDisposable subscription =
query
.ObserveOnDispatcher()
.Subscribe(audioItem =>
{
/* Your code to update the UI here */
});
It was unclear to me whether it's creating a new WAVEFile or the call to AudioItemMatchesFilter that is taking the time so I wrapped both in Observable.Start. You also had a ref call and that makes the code a bit messy.
Nonetheless, this code handles all of the calls in the background and automatically moves the results as they come in to the dispatcher thread.
I've followed the pattern defined at Async OOP to create a view model which reads data from 2 sources. However, when I attempt to setup CollectionViewSource and Filters for the collection I get an exception Additional information: Must create DependencySource on same Thread as the DependencyObject. or when filtering the data I get the following exception The calling thread cannot access this object because a different thread owns it.
The properties are defined as follows
public NotifyTaskCompletion InitializationNotifier{ get; set; }
public Task Initialization => InitializationNotifier.Task;
The viewmodel constructor defines the InitializationNotifier as follows:
InitializationNotifier = new NotifyTaskCompletion(InitializeAsync());
The task is defined as below:
private async Task InitializeAsync()
{
var aDataSource = await Task.Run(() => ADataSource.Get(1, 2));
var bDataSource = await Task.Run(() => new BDataSource(1, 2);
PrepareData(aDataSource, bDataSource); // creates List<T> for different categories
SetupCollections(); // Creates Observable collections from List<T>
SetupCVSAndFilters(); // Creates CollectionViewSource for different categories
}
private void SetupCollections()
{
AStars = new ObservableCollection<IAEntity>(m_aStars);
BStars = new ObservableCollection<IAEntity>(m_bStars);
}
// Setup Collectionview source and Filters
// Get an exception:
// Additional information: Must create DependencySource on same Thread as the DependencyObject.
private void SetupCVSAndFilters()
{
AStarsCVS = new CollectionViewSource { Source = AStars };
BStarsCVS = new CollectionViewSource { Source = BStars };
AStarsCVS.View.Filter = FilterCompareData;
BStarsCVS.View.Filter = FilterCompareData;
}
The view/viewmodel works well without using Async/Task, but the fetches from the datasources are time-consuming(PInvokes) and I have multiple tabs which read different data and I'd appreciated pointers to help me understand where I'm going wrong with the usage of Task/Async
Every time you call Task.Run() a new thread is created. The exceptions you're getting have to do with your threads trying to access each other in ways they're not supposed to. I don't think I can see where it's going wrong just by the code you've provided, but I can tell you that in WPF, threads can access the objects of other threads using Dispatcher.BeginInvoke().
It also looks like you may not be getting the benefits of asynchronous programming that you're looking for. Having two await statements one after the other like that means the first task will complete before the second one even starts. I think you want Task.WaitAll().
public Form1()
{
InitializeComponent();
loadComboEmail();
}
private void loadComboEmail()
{
string path = Directory.GetCurrentDirectory();
string build = (path + "\\" + "email.txt");
string[] lines = System.IO.File.ReadAllLines(build);
comboEmail.Items.AddRange(lines);
comboEmail.SelectedIndex=0;
}
I've got a combobox that I wanted to load with the client email addresses which are stored in an text file. Using the loadComboEmail() I read from the text file and load the Combobox. Am I doing something that is considered bad practice by calling the loadComboEmail at the form startup? If so, how do I properly read from the textfile and then load it into the combo, so when the form loads, it has the necessary data?
No, this seems quite legit. You don't have to worry as this is the way all things get loaded in WinForms... This will block the UI thread for a short amount of time, but since your not going to load huge masses of something, you won't even notice it.
When you start performing a greater amount of Actions and big files you should think about using a BackgroundWorker or a Task!
Anyway you should also consider using the following instead of your Code:
private void loadComboEmail()
{
string path = System.IO.Path.GetDirectoryName(Application.ExecutablePath); //seems to be the same to me, but is safer
string build = System.IO.Path.Combine(path, "email.txt"); //much more safer then simple string addition
string[] lines = System.IO.File.ReadAllLines(build);
comboEmail.Items.AddRange(lines);
comboEmail.SelectedIndex = 0;
}
You can use:
Task task = Task.Factory.StartNew(() =>
{
// Background work
loadComboEmail();
}).ContinueWith((t) =>
{
//If you need to execute something after the loading process.
});
In order to update the UI thread, you can maybe do the reading on another thread and when the list of emails is loaded, just update it.
Task.Factory.StartNew(() =>
{
// Background work - read the file
}).ContinueWith((t) => {
// Update UI thread here
}, TaskScheduler.FromCurrentSynchronizationContext());
Using the constructor to load that is not considered as a bad practice. Read the answer of Hans Passant about when you should use the Load Event of the window. What setup code should go in Form Constructors versus Form Load event?
Altough as said in comments, you are blocking the UI Thread. In the constructor the keyword await cannot be used. So you have to 'Fire and forget'. When using the Load event you can use await but event handlers are async void. So they are not awaited, you have stil 'Fire and forget'.
public Form1()
{
InitializeComponent();
loadComboEmail();
}
private async Task loadComboEmail()
{
string path = Directory.GetCurrentDirectory();
string build = (path + "\\" + "email.txt");
string[] lines = await Task.Run(() => File.ReadAllLines(build));
comboEmail.Items.AddRange(lines);
comboEmail.SelectedIndex=0;
}
Still trying to wrap my head around async/await. I have the following method for drag/drop loading:
private async void p_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
List<string> CurvesToLoad = new List<string>();
List<string> TestsToLoad = new List<string>();
foreach (string file in files)
{
if (file.ToUpper().EndsWith(".CCC"))
CurvesToLoad.Add(file);
else if (file.ToUpper().EndsWith(".TTT"))
TestsToLoad.Add(file);
}
//SNIPPET IN BELOW SECTION
foreach (string CurvePath in CurvesToLoad)
{
Curve c = new Curve(CurvePath);
await Task.Run(() =>
{
c.load();
c.calculate();
});
AddCurveControls(c);
}
//END SNIPPET
foreach (string TestPath in TestsToLoad)
{
Test t = new Test(TestPath);
await Task.Run(() =>
{
t.load();
});
AddTestControls(t);
}
}
It is non-blocking as I expected - I am able to navigate between tabs of the TabControl as multiple items are loaded and I can see each tab pop up as it complete loading.
I then tried to convert to this:
private Task<Curve> LoadAndCalculateCurve(string path)
{
Curve c = new Curve(path);
c.load();
c.calculate();
return Task.FromResult(c);
}
And then replace the marked snippet from the first code block with:
foreach (string CurvePath in CurvesToLoad)
{
Curve c = await LoadAndCalculateCurve(CurvePath);
AddCurveControls(c);
}
And it becomes blocking - I can't navigate through tabs as it's loading, and then all of the loaded items appear at once when they are completed. Just trying to learn and understand the differences at play here - many thanks in advance.
EDIT:
Updated LoadAndCalculateCurve():
private async Task<Curve> LoadAndCalculateCurve(string path)
{
Curve c = new Curve(path);
await Task.Run(() => {
c.load();
c.calculate();
});
return c;
}
Async methods do not execute in a different thread, await does not start a thread. async merely enables the await keyword and await waits for something that already runs.
All of that code is running on the UI thread.
So basically this is what is happening to my knowledge.
In your first code you write
foreach (string CurvePath in CurvesToLoad)
{
Curve c = new Curve(CurvePath);
await Task.Run(() =>
{
c.load();
c.calculate();
});
AddCurveControls(c);
}
this does the async flow as expected because you are using the Task.Run which adds the work to the ThreadPool queue.
In your second try you don't do this and you are using the same task. Try to use the same logic of (Task.Run) in the second try and I think it will work
The implementation of your LoadAndCalculateCurve method is to synchronously create a Curve, synchronously load it and perform the calculation, and then return the result wrapped in a Task. Nothing about this is asynchronously. When you await it it will invoke the method, do all of the synchronous work to get the (already completed) task, and then add a continuation to that (already completed) task that will fire immediately.
When you instead use Task.Run you're scheduling those long running operations to take place in another thread and immediately returning a (not yet completed) Task that you can use to run code when it does eventually finish its work.
In my MVVM application my view model calls 3 different service methods, converts the data from each into a common format and then updates the UI using property notification/observable collections etc.
Each method in the service layer starts a new Task and returns the Task to the view model. Here's an example of one of my service methods.
public class ResourceService
{
internal static Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback)
{
var t = Task.Factory.StartNew(() =>
{
//... get resources from somewhere
return resources;
});
t.ContinueWith(task =>
{
if (task.IsFaulted)
{
errorCallback(task.Exception);
return;
}
completedCallback(task.Result);
}, TaskScheduler.FromCurrentSynchronizationContext());
return t;
}
}
Here's the calling code and other relevant parts of the view model...
private ObservableCollection<DataItem> Data = new ObservableCollection<DataItem>();
public ICollectionView DataView
{
get { return _dataView; }
set
{
if (_dataView != value)
{
_dataView = value;
RaisePropertyChange(() => DataView);
}
}
}
private void LoadData()
{
SetBusy("Loading...");
Data.Clear();
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
Task.WaitAll(tasks);
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}
private Task LoadResources()
{
return ResourceService.LoadResources(resources =>
{
foreach(var r in resources)
{
var d = convertResource(r);
Data.Add(d);
}
},
error =>
{
// do some error handling
});
}
This almost works but there are a couple of small issues.
Number 1: In the call to SetBusy at the very beginning, before I start any tasks and before I call WaitAll, I set the IsBusy property to true. This should update the UI and show the BusyIndicator control but it's not working. I've also tried adding simple string properties and binding those and they're not being updated either. The IsBusy functionality is part of a base class and works in other view models where I don't have more than one Task running so I don't believe there is an issue with the property notification or data binding in the XAML.
All the data bindings seem to be updated after the whole method has completed. I'm not seeing any "first time exceptions" or binding errors in the output Window which is leading me to believe the UI thread is somehow being blocked before the call to WaitAll.
Number 2: I seem to be returning the wrong Tasks from the service methods. I want everything after WaitAll to run after the view model has converted all the results from all the service methods in the callbacks. However if I return the continuation task from the service method the continuation never gets called and WaitAll waits forever. The strange thing is the UI control bound to the ICollectionView actually displays everything correctly, I assumed this is because Data is an observable collection and the CollectionViewSource is aware of the collection changed events.
You can use TaskFactory.ContinueWhenAll to build a continuation that runs when the input tasks all complete.
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
Task.Factory.ContinueWhenAll(tasks, t =>
{
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}, CancellationToken.None, TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
Note that this becomes simpler if you use C# 5's await/async syntax:
private async void LoadData()
{
SetBusy("Loading...");
Data.Clear();
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
await Task.WhenAll(tasks);
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}
However if I return the continuation task from the service method the continuation never gets called and WaitAll waits forever
The problem is that your continuation task requires the UI thread, and you're blocking the UI thread in the WaitAll call. This creates a deadlock which will not resolve.
Fixing the above should correct this - you'll want to return the Continuation as the Task, as that's what you need to wait for completion - but by using TaskFactory.ContinueWhenAll you free up the UI thread so it can process those continuations.
Note that this is another thing that gets simplified with C# 5. You can write your other methods as:
internal static async Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback)
{
try
{
await Task.Run(() =>
{
//... get resources from somewhere
return resources;
});
}
catch (Exception e)
{
errorCallback(task.Exception);
}
completedCallback(task.Result);
}
That being said, it's typically better to write the methods to return a Task<T> instead of providing callbacks, as that simplifies both ends of the usage.