Update DataContext from background thread - wpf

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");.

Related

WPF: Waiting for an ItemContainerGenerator to have its containers ready

I'm writing a WPF control that contains an ItemsControl. The control adds and removes items based on certain user actions. Once an item has been added, the control needs to access a FrameworkElement inside the ItemTemplate instance that was just created.
I'm using ItemContainerGenerator.ContainerFromIndex to do this. I also get a ContentPresenter back, but it is empty: it appears it takes a few milliseconds on a separate thread to instantiate the template objects.
I read that I need to use ItemContainerGenerator.Status to determine whether or not the containers are fully created, so I wrote the following method:
private async Task<TextBox> GetMainInputControl(int index)
{
// _selectedItemsEditor is the ItemsControl inside my main control that contains the items
var evt = new ManualResetEvent(false);
_selectedItemsEditor.ItemContainerGenerator.StatusChanged += (sender, args) =>
{
var status = _selectedItemsEditor.ItemContainerGenerator.Status;
if (status == GeneratorStatus.ContainersGenerated || status == GeneratorStatus.Error)
{
evt.Set();
}
};
ContentPresenter container = null;
await Task.Run(() =>
{
var status = _selectedItemsEditor.ItemContainerGenerator.Status;
if (status == GeneratorStatus.GeneratingContainers
|| status == GeneratorStatus.NotStarted)
{
evt.WaitOne();
}
container =
_selectedItemsEditor.ItemContainerGenerator.ContainerFromIndex(index) as ContentPresenter;
});
return container?.ContentTemplate.FindName("PART_ItemEditorMainInput", container) as TextBox;
}
I know that there are a few things I need to fix here, but mostly, it just doesn't work, because _selectedItemsEditor.ItemContainerGenerator.Status immediately returns GeneratorStatus.ContainersGenerated, so the code doesn't wait - but then the code container?.ContentTemplate.FindName throws an exception indicating that the container is NOT ready.
How can I make this work, or alternatively use a better way of achieving this?
That code looks like you're trying to access ui controls on a background thread. So I'm not at all surprised it doesn't work.
There are two approaches I would consider.
You could defer your code so it waits until the dispatcher ( the ui thread essentially ) has done it's stuff for whatever you just asked it to do.
Application.Current.Dispatcher.InvokeAsync(new Action(() =>
{
// Your code which is to run after the items are rendered
}), DispatcherPriority.ContextIdle);
Or
You could force the layout process so you make the items do their thing. This will potentially lock the ui up whilst it's working. If the user clicks something and his obvious intent is to wait for layout to update or there's not so much going on then this won't be a problem.
You could just call .UpdateLayout() on your control.
https://msdn.microsoft.com/en-us/library/system.windows.uielement.updatelayout(v=vs.110).aspx

Replacing methods that use backgroundworker to async / tpl (.NET 4.0)

My questions are many. Since I saw. NET 4.5, I was very impressed. Unfortunately all my projects are .NET 4.0 and I am not thinking about migrating. So I would like to simplify my code.
Currently, most of my code that usually take enough time to freeze the screen, I do the following:
BackgroundWorker bd = new BackgroundWorker();
bd.DoWork += (a, r) =>
{
r.Result = ProcessMethod(r.Argument);
};
bd.RunWorkerCompleted += (a, r) =>
{
UpdateView(r.Result);
};
bd.RunWorkerAsync(args);
Honestly, I'm tired of it. And that becomes a big problem when there is a logic complex user interaction.
I wonder, how to simplify this logic? (Remember that I'm with. Net 4.0) I noticed a few things by google, but not found anything easy to implement and suitable for my needs.
I thought this solution below:
var foo = args as Foo;
var result = AsyncHelper.CustomInvoke<Foo>(ProcessMethod, foo);
UpdateView(result);
public static class AsyncHelper
{
public static T CustomInvoke<T>(Func<T, T> func, T param) where T : class
{
T result = null;
DispatcherFrame frame = new DispatcherFrame();
Task.Factory.StartNew(() =>
{
result = func(param);
frame.Continue = false;
});
Dispatcher.PushFrame(frame);
return result;
}
}
I am not sure about the impact is on manipulating the dispatcher frame.
But I know That it would work very well, for example, I could use it in all the events of controls without bothering to freeze the screen.
My knowledge about generic types, covariance, contravariance is limited, maybe this code can be improved.
I thought of other things using Task.Factory.StartNew and Dispatcher.Invoke, but nothing that seems interesting and simple to use. Can anyone give me some light?
You should just use the Task Parallel Library (TPL). The key is specifying the TaskScheduler for the current SynchronizationContext for any continuations in which you update the UI. For example:
Task.Factory.StartNew(() =>
{
return ProcessMethod(yourArgument);
})
.ContinueWith(antecedent =>
{
UpdateView(antecedent.Result);
},
TaskScheduler.FromCurrentSynchronizationContext());
Aside from some exception handling when accessing the antecedent's Result property, that's all there is too it. By using FromCurrentSynchronizationContext() the ambient SynchronizationContext that comes from WPF (i.e. the DispatcherSynchronizationContext) will be used to execute the continuation. This is the same as calling Dispatcher.[Begin]Invoke, but you are completely abstracted from it.
If you wanted to get even "cleaner", if you control ProcessMethod I would actually rewrite that to return a Task and let it own how that gets spun up (can still use StartNew internally). That way you abstract the caller from the async execution decisions that ProcessMethod might want to make on its own and instead they only have to worry about chaining on a continuation to wait for the result.
UPDATE 5/22/2013
It should be noted that with the advent of .NET 4.5 and the async language support in C# this prescribed technique is outdated and you can simply rely on those features to execute a specific task using await Task.Run and then execution after that will take place on the Dispatcher thread again automagically. So something like this:
MyResultType processingResult = await Task.Run(() =>
{
return ProcessMethod(yourArgument);
});
UpdateView(processingResult);
How about encapsulating the code that is always the same in a reusable component? You could create a Freezable which implements ICommand, exposes a property of Type DoWorkEventHandler and a Result property. On ICommand.Executed, it would create a BackgroundWorker and wire up the delegates for DoWork and Completed, using the value of the DoWorkEventHandler as event handler, and handling Completed in a way that it sets its own Result property to the result returned in the event.
You'd configure the component in XAML, using a converter to bind the DoWorkEventHandler property to a method on the ViewModel (I assume you've got one), and bind your View to the component's Result property, so it gets updated automatically when Result does a change notification.
The advantages of this solution are: it is reusable, and it works with XAML only, so no more glue code in your ViewModel just for handling BackgroundWorkers. If you don't need your background process to report progress, it could even be unaware that it runs on a background thread, so you can decide in the XAML whether you want to call a method synchronously or asynchronously.
A few months have passed, but could this help you?
Using async/await without .NET Framework 4.5

WPF issue with updating

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.

Can I set the culture of a silverlight application from a page class?

In my SL application, I have a page that connects to a web service to retrieve some culture information such as date formats. As this information is not known at the instantiation of the silverlight application, I cannot set this in the public App() constructor. The following lines don't seem to work in the async completed method of the service call within the page class:
var dateFormatString = e.Result.DateFormatString;
Thread.CurrentThread.CurrentCulture = (CultureInfo)Thread.CurrentThread.CurrentCulture.Clone();
Thread.CurrentThread.CurrentCulture.DateTimeFormat.ShortDatePattern = dateFormatString;
Is there a way to set the culture of the silverlight application from a page class?
Set it in the main thread (your asyncCompleted is probably running on a different thread).
Let me know if you do not know how to do this.
There is no need to clone the Culture. (line 2 in your code).
Just set it directly:
var dateFormatString = e.Result.DateFormatString;
Thread.CurrentThread.CurrentCulture.DateTimeFormat.ShortDatePattern = dateFormatString;
** EDIT added sample code to call from UI Thread.
You can always pass state to an async wcf call. In this case I am passing a Dispatcher.
Once the callback is called use the Dispatcher to change the UI Culture in the UI thread.
I wrote this off the top of my head, but should be sufficient to give you an idea of what you need to do.
public void SetCulture()
{
YourWcfProxy.GetCultureInfoCompleted += GetCultureInfoCompleted;
object state = Dispatcher; // variable just for clarity.
YourWcfProxy.GetCultureInfoAsync(state);
}
private void GetCultureInfoCompleted(object sender, GetCultureInfoCompletedCompletedEventArgs e)
{
Dispatcher dispatcher = e.UserState as Dispatcher;
dispatcher.BeginInvoke(() =>
{
// set the ui culture here!
}
);
}
** EDIT 2 (answer question)
If you have a UIElement handy, you can use it's Disptacher property.
Otherwise use the static:
Deployment.Current.Dispatcher

ADO.NET DataServices with Prism

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.

Resources