I have a WPF application developed with Prism. I am facing a typical scenario for a code change I implemented for performance improvement.
I have a UI Grid which shows a list of entities. I select some 10 entities in the Grid and try to open it, which inturn should open a new window and load the corresponding 10 entities by resolving corresponding containers and their dependencies.
The sample code is below,,, I have commented the operations that are occuring..
private void OpenSelectedEntities(List<Entity> entities)
{
foreach (Entity entity in entities)
{
if (Application.Current != null)
{
//I implemented opening entity in async so that 10 entities will be opened without stalling the UI.
Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() =>
{
OpenEntity(entity, false);
}));
}
}
}
public void OpenEntity(Entity entity, boolean val)
{
//Creating Container to load the controls.
//Adding Container to Region
//Loading the other UI related and triggering operation threads
}
The problem I am facing is, When I select some entities and verify it in my local development environment, I am able to see all the 10 entities opened and working well. However, When I deploy the code in Server and test it there,, Only two entities were opened, the first entity and last entity in the selected entities.. I am completely clueless what is causing this problem. I have cross checked twice, I am getting this problem while calling OpenEntity as Asynchronous.
Has anyone encountered situation like this using WPF and Prism.
It is an "Access to Modified Closure" problem. Look into that for more details.
Here's a blog post that discusses it. http://www.jarloo.com/access-to-modified-closure/
As for the fix, create a temp variable.
private void OpenSelectedEntities(List<Entity> entities)
{
foreach (Entity entity in entities)
{
if (Application.Current != null)
{
Entity tempEntity = entity;
Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() =>
{
OpenEntity(tempEntity , false);
}));
}
}
}
Related
I fetch data for a wpf window in a backgroundthread like this [framework 4.0 with async/await]:
async void refresh()
{
// returns object of type Instances
DataContext = await Task.Factory.StartNew(() => serviceagent.GetInstances());
var instances = DataContext as Instances;
await Task.Factory.StartNew(() => serviceagent.GetGroups(instances));
// * problem here * instances.Groups is filled but UI not updated
}
When I include the actions of GetGroups in GetInstances the UI shows the groups.
When I update in a seperate action the DataContext includes the groups correclty but the UI doesn't show them.
In the GetGroups() method I inlcuded NotifyCollectionChangedAction.Reset for the ObservableCollection of groups and this doesn't help.
Extra strange is that I call NotifyCollectionChangedAction.Reset on the list only once, but is executed three times, while the list has ten items?!
I can solve the issue by writing:
DataContext = await Task.Factory.StartNew(() => serviceagent.GetGroups(instances));
But is this the regular way for updating DataContxt and UI via a backgound process?
Actually I only want to update the existing DataContext without setting it again?
EDIT: serviceagent.GetGroups(instances) in more detail:
public void GetGroups(Instances instances)
{
// web call
instances.Admin = service.GetAdmin();
// set groups for binding in UI
instances.Groups = new ViewModelCollection<Groep>(instances.Admin.Groups);
// this code has no effect
instances.Groups.RaiseCollectionChanged();
}
Here ViewModelCollection<T> inherits from ObservableCollection<T> and I added the method:
public void RaiseCollectionChanged()
{
var handler = CollectionChanged;
if (handler != null)
{
Trace.WriteLine("collection changed");
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
handler(this, e);
}
}
There's a few points that stand out in the async portion of your code:
I explain why we should avoid async void in my MSDN article. In summary, void is an unnatural return type for async methods, so it has some quirks, particularly around exception handling.
We should prefer TaskEx.Run over StartNew for asynchronous tasks, as I explain on my blog.
While not exactly required, it's a good idea to follow the guidelines in the Task-based Asynchronous Pattern; following those naming conventions (etc) will help other developers to maintain the code.
Based on these, I also recommend my intro to async blog post.
On to the actual problem...
Updating data-bound code from background threads is always tricky. I recommend that you treat your ViewModel data as though it were part of the UI (it is a "logical UI", so to speak). So it's fine to retrieve data on a background thread, but updating the actual VM values should be done on the UI thread.
These changes make your code look more like this:
async Task RefreshAsync()
{
var instances = await TaskEx.Run(() => serviceagent.GetInstances());
DataContext = instances;
var groupResults = await TaskEx.Run(() => serviceagent.GetGroups(instances));
instances.Admin = groupResults.Admin;
instances.Groups = new ObservableCollection<Group>(groupResults.Groups);
}
public GroupsResult GetGroups(Instances instances)
{
return new GroupsResult
{
Admin = service.GetAdmin(),
Groups = Admin.Groups.ToArray(),
};
}
The next thing you need to check is whether Instances implements INotifyPropertyChanged. You don't need to raise a Reset collection changed event when setting Groups; since Groups is a property on Instances, it's the responsibility of Instances to raise INotifyPropertyChanged.PropertyChanged.
Alternatively, you could just set DataContext last:
async Task RefreshAsync()
{
var instances = await TaskEx.Run(() => serviceagent.GetInstances());
var groupResults = await TaskEx.Run(() => serviceagent.GetGroups(instances));
instances.Admin = groupResults.Admin;
instances.Groups = new ObservableCollection<Group>(groupResults.Admin.Groups);
DataContext = instances;
}
Seems there's a bit of confusion on what DataContext is. DataContext is not some special object that you have to update. It's a reference to the object or objects that you want to bind to your UI. Whenever you make changest to these objects, the UI get's notified (if you implement the proper interfaces).
So, unless you explicitly change the DataContext, your UI can't guess that now you want to show a different set of objects.
In fact, in your code, there is no reason to set the DataContext twice. Just set it with the final set of objects you want to display. In fact, since you work on the same data, there is no reason to use two tasks:
async Task refresh()
{
// returns object of type Instances
DataContext=await Task.Factory.StartNew(() => {
var instances = serviceagent.GetInstances();
return serviceagent.GetGroups(instances);
});
}
NOTE:
You should neer use the async void signature. It is used only for fire-and-forget event handlers, where you don't care whether they succeed or fail. The reason is that an async void method can't be awaited so no-one can know whether it succeeded or not.
I discovered that RaiseCollectionChanged has no influence on the property Groups where the DataContext is bound to. I simply have to notify: instances.RaisePropertyChanged("Groups");.
I've got an ObservableCollection in a ViewModel that I need to modify in code on a background thread (using a Task object). I've also got an ICollectionView attached to that collection. When I modify the collection at run-time, the code runs fine. In unit testing I get the error, "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread."
I create the collection thusly (on the UI thread at run-time):
CashDeliveryDepots = new ObservableCollection<CashDeliveryDepot>();
FilteredCashDeliveryDepots = CollectionViewSource.GetDefaultView(CashDeliveryDepots);
and modify it using this code (which will be running on a non-UI thread at run-time):
CurrentDispatcher.Invoke(() =>
{
foreach (var depot in depots)
{
CashDeliveryDepots.Add(depot);
}
});
The CurrentDispatcher class executes code according to whether or not there is an application dispatcher.
internal static class CurrentDispatcher
{
internal static void Invoke(Action action)
{
if (Application.Current != null)
Application.Current.Dispatcher.Invoke(action);
else
action();
}
}
So at run-time the dispatcher is picked up correctly, the observable collection is updated and the filtered view updates too. When running the unit tests I get the exception at the point of trying to add a new item to the collection.
Can anyone suggest a way around this so I can get my tests working?
I use Entity Framework 4 and Self Tracking Entities. The schema is like:
Patient -> Examinations -> LeftPictures
-> RightPictures
So there is TrackableCollection of these two relationships Patient 1 - * ....Pictures.
Now when loading the customers Form and browsing the details I dont need to load these
data images, only when another form is loaded for Examination details!
I am using a class library as a Data Repository to get data from the database (SQL Server) and this code:
public List<Patient> GetAllPatients()
{
try
{
using (OptoEntities db = new OptoEntities())
{
List<Patient> list = db.Patients
.Include("Addresses")
.Include("PhoneNumbers")
.Include("Examinations").ToList();
list.ForEach(p =>
{
p.ChangeTracker.ChangeTrackingEnabled = true;
if (!p.Addresses.IsNull() &&
p.Addresses.Count > 0)
p.Addresses.ForEach(a => a.ChangeTracker.ChangeTrackingEnabled = true);
if (!p.PhoneNumbers.IsNull() &&
p.PhoneNumbers.Count > 0)
p.PhoneNumbers.ForEach(a => a.ChangeTracker.ChangeTrackingEnabled = true);
if (!p.Examinations.IsNull() &&
p.Examinations.Count > 0)
p.Examinations.ForEach(e =>
{
e.ChangeTracker.ChangeTrackingEnabled = true;
});
});
return list;
}
}
catch (Exception ex)
{
return new List<Patient>();
}
}
Now I need when calling the Examination details form to go and get all the Images for the Examination relationship (LeftEyePictures, RightEyePictures). I guess that is called Lazy Loading and I dont understood how to make it happen while I'm closing the Entities connection immidiately and I would like to stay like this.
I use BindingSource components through the application.
What is the best method to get the desired results?
Thank you.
Self tracking entities don't support lazy loading. Moreover lazy loading works only when entities are attached to live context. You don't need to close / dispose context immediately. In case of WinForms application context usually lives for longer time (you can follow one context per form or one context per presenter approach).
WinForms application is scenario for normal attached entities where all these features like lazy loading or change tracking work out of the box. STEs are supposed to be used in distributed systems where you need to serialize entity and pass it to another application (via web service call).
I have a strange issue. I wonder whether it's a standard behavior of the .NET components, and how to handle this.
Our app is using galasoft mvvm light. I have a form with a tree view, which is getting the data via an asynchronous call. And why that asynchronous task is running, we're showing a progress bar to the user. I'm using ObservableCollection as a collection for my tree structure. Now the problem:
This piece of code gives us the info:
public Task<ObservableCollection<FillingTreeNode>> GetTreeStructureAsync(SyncSettings settings)
{
SearchRequest request = BuildRequest();
return searchService.SearchRecordsAsync(request).ContinueWithConversion(
records => new ObservableCollection<FillingTreeNode>(records
.Select(cabinet => new FillingTreeNode
{
IsChecked = false,
DisplayName = cabinet.Fields[Fields.CabinetName].Value,
Node = cabinet.AsFillingNode(FillingNodeType.Cabinet),
NumberOfNodes = SendXmlRequest(record),
Children = new ObservableCollection<FillingTreeNode>(GetChildren (record));
}
}
This is the task extension to convert the result to some new type:
public static Task<TNew> ContinueWithConversion<TOld, TNew>(this Task<TOld> task, Func<TOld, TNew> conversionAction)
{
return task.ContinueWith(completedTask => conversionAction(task.Result));
}
Now the issue. The data is loaded from the server, the UI (the progress bar) says that the data is loaded, and only after that SendXmlRequest(record) (which is a bit long to wait) begins to work! But i expect that it's already done. The user sees nothing until those functions are finished working
Do you know what is the cause of the problem? Can that be the behavior of the Observable collection? How can i fix it?
Thank in advance.
I've start using prism with silverlight 3, but, we are trying to implement it to work with ADO.NET DataServices. The "DataServiceQuery" query type required to use with Silverlight, requires a Asyncronous call to be fired after the query. This will break ous Prism Pattern by what I can see.
Any ideas to get only the data of the query to use in Prism Pattern? Correct-me anyone if i'm wrong!
Making an Asynchronous call to your server doesn't break "Prism Pattern". When your view needs to query the server, its viewmodel fires an asynchronous request and provides a callback. Once callback is called, it handles the result and updates whatever properties it exposes to a view. This will result in view updating according to bindings you set up in your xaml.
PL is exactly right. There's really no patterns that Prism encourages that are incompatible with ADO.NET Data Services. There are just a few things you should know.
Here's a small sample. It's a little tricky... the complete event will sometimes fire outside of the UI thread, so you have to handle it with the dispatcher (at least in SL2 you did):
public class MyViewModel : BaseViewModel
{
public Customer CustomerResult
{
...
}
NorthwindEntities svcContext = null;
public MyViewModel()
{
svcContext =
new NorthwindEntities(new Uri("Northwind.svc", UriKind.Relative));
DataServiceQuery<Customers> query =
svcContext.Customers.Expand("Orders");
// Begin the query execution.
query.BeginExecute(WorkComplete, query);
}
private void WorkComplete(IAsyncResult result)
{
DataServiceQuery<Customers> query =
result.AsyncState as DataServiceQuery<Customers>;
Customers returnedCustomer =
query.EndExecute(result).FirstOrDefault();
//Execute with the dispatcher
Dispatcher.CurrentDispatcher.BeginInvoke( () =>
{
CustomerResult = returnedCustomer;
});
}
}
Of course there is no exception handling in here, but you get the picture hopefully.