I bound a button to a command in the view model that triggers a long-running operation. The operation is run in a separate task. Before and after the task, a property is set to reflect it running in the UI.
My problem now is: Sometimes, when I click the button, the change of the property BEFORE the long operation runs is registered (i.e., the UI changes accordingly), but when the task finishes, it is not immediately. I have to click somewhere in the UI to make the change to be reflected.
Here is how I did it (using MVVM Light):
_longRunningOpCommand = new RelayCommand(async () => await DoLongRunningThingAsync(), CanDoLongRunningThing);
// ...
public ICommand LongRunningOpCommand { get { return _longRunningOpCommand; } }
// ...
private async Task DoLongRunningThingAsync() {
try {
IsDoingStuff = true; // triggers a PropertyChangeEvent, is bound to UI
await Task.Factory.StartNew(async () => {
await _something.DoLenghtyOperation();
}, TaskCreationOptions.LongRunning);
} finally {
IsDoingStuff = false;
}
}
Now, the UI elements update as soon as IsDoingStuff is becoming false as they should, but they are not updated when IsDoingStuff is becoming true. I have to click into the UI to have them updated.
I think I'm doing something wrong, but can't track down what.
Related
I have a UWP app in which one of the pages needs to do three tasks - the first is to load the main content for the page (a collection of 'Binders' objects retrieved from our API) then after that load some other content which are not dependent on the first task in any way.
My page is backed with a ViewModel (I'm using the default Template10 MVVM model) and when the page is navigated to I do this in the VM OnNavigatedToAsync method:
public async override Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
if (mode == NavigationMode.New || mode == NavigationMode.Refresh)
{
IsBusy = true; //Show progress ring
CreateServices(); //Create API service
//Download binders for board and populate ObservableCollection<Binder>
//This has a cover image and other info I want to show in the UI immediately
await PopulateBinders();
//Get files and calendar events for board
//Here I want to run this on a different thread so it does
//not stop UI from updating when PopulateBinders() is finished
await Task.WhenAll(new[]
{
PopulateBoardFiles(),
PopulateBoardEvents()
});
IsBusy = false;
await base.OnNavigatedToAsync(parameter, mode, state);
return;
}
}
So the main task is PopulateBinders() - this calls the API, returns the data and loads it into an ObservableCollection of Binder. When this has run I want the UI to update it's bindings and show the Binder objects immediately but instead it waits until the two other tasks in the WhenAll Task) have run before updating the UI. (All three of these Tasks are defined as private async Task<bool>...)
I realise I'm missing something basic here - but I thought calling a Task from an async method would allow the UI to update? Since it clearly doesn't how do I refactor this to make my page bindings update after the first method?
I tried Task.Run(() => PopulateBinders()); but it made no difference.
Instead of running it inside OnNavigatedToAsync(), run the asynchronous task when the page is already loaded since you are unintentionally "block" the app to run base.OnNavigatedToAsync() for several seconds untill Task.WhenAll finished running.
Running on loaded event in MVVM can be achieved by implementing Microsoft.Xaml.Interactivity to bind Page.Loaded event with a DelegateCommand class in your viewmodel.
XAML Page ( assuming you are using Prism as your MVVM framework )
<Page ...
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Loaded">
<core:InvokeCommandAction Command="{x:Bind Path=Vm.PageLoaded}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Page>
and inside your viewmodel:
public class PageViewModel : ... //some interface or else
{
public DelegateCommand PageLoaded;
public PageViewModel(...)
{
PageLoaded = new DelegateCommand(async () =>
{
IsBusy = true;
CreateServices();
await PopulateBinders();
await Task.WhenAll(new[]
{
PopulateBoardFiles(),
PopulateBoardEvents()
});
IsBusy = false;
});
}
}
Read more : Binding UWP Page Loading/ Loaded to command with MVVM
I hope this code will help you to update the UI as expected:
public async override Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
if (mode == NavigationMode.New || mode == NavigationMode.Refresh)
{
IsBusy = true; //Show progress ring
CreateServices(); //Create API service
//Download binders for board and populate ObservableCollection<Binder>
//This has a cover image and other info I want to show in the UI immediately
await PopulateBinders();
await PouplateBoardData();
await base.OnNavigatedToAsync(parameter, mode, state);
return;
}
}
private async void PopulateBoardData()
{
await Task.WhenAll(new[]
{
PopulateBoardFiles(),
PopulateBoardEvents()
});
IsBusy = false;
}
The following code works perfectly. It shows the spinner on the UI, starts a task using a thread from the threadpool and runs the heavy operation, once complete, logic to hide the spinner executes on the main thread as intended.
public void LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
Task.Run(async () =>
{
var customers = await this.custService.GetCustomers();
// code truncated for clarity
Device.BeginInvokeOnMainThread(() =>
{
// Update UI to hide spinner
this.LoadingCustomers = false;
});
});
}
My question; Is there a better way to write this logic using ContinueWith/ConfigureAwait options? Using these options seems to block the UI thread. In the example below, shouldn't the UI thread continue running the UI logic (animating the spinner/user input) and then come back to complete the logic inside the ContinueWith?
public void LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
this.custService.GetCustomers().ContinueWith((t) =>
{
var customers = t.Result;
// code truncated for clarity
// Update UI to hide spinner
this.LoadingCustomers = false;
});
}
As requested in the comments, here is the code for GetCustomers. the dbContext is EntityFrameworkCore.
public async Task<List<CustomerModel>> GetCustomers()
{
return await this.dbContext.Customers.ToListAsync();
}
UPDATE
The answer by FCin is correct, however; the cause root of this seems to be with EFCore and ToListAsync, it isn't running asynchronously.
Proper way of writing such method is to use async/await from start to finish. Right now you are doing fire and forget meaning if there is exception inside Task.Run you will never know about it. You should start from an event handler. This can be whatever, mouse click, page loaded, etc.
private async void MouseEvent_Click(object sender, EventArgs args)
{
await LoadCustomers();
}
public async Task LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
// We don't need Device.BeginInvokeOnMainThread, because await automatically
// goes back to calling thread when it is finished
var customers = await this.custService.GetCustomers();
this.LoadingCustomers = false;
}
There is an easy way to remember when to use Task.Run. Use Task.Run only when you do something CPU bound, such as calculating digits of PI.
EDIT: #bradley-uffner suggested to just write the following:
public async Task LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
var customers = await this.custService.GetCustomers();
// code truncated for clarity
// you are still on UI thread here
this.LoadingCustomers = false;
}
How about this:
public async Task LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
await Task.Run(async () =>
{
var customers = await this.custService.GetCustomers();
// code truncated for clarity
});
this.LoadingCustomers = false;
}
The code after await is executed on the current thread so it should work out of the box.
I'm building an MVVM Light WPF app in Visual Studio 2015. It has a user control that contains a WindowsFormsHost with a ReportViewer for SQL Server Reporting Services local report. A button in ReportView.xaml calls a command, which in turn sends a message to the code-behind of MasterListView.xaml to generate the report.
Here's the command called by the button in ReportViewModel.cs:
public ICommand RunReportRelayCommand =>
new RelayCommand(async ()=> { await RunReport(); });
private async Task RunReport()
{
try
{
IsBusy = true;
await Task.Run(() =>
{
Messenger.Default.Send(true, "RunMasterListReport");
});
}
finally
{
IsBusy = false;
}
}
Here's the definition of IsBusy property in ReportViewModel.cs:
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
if (value == _isBusy) return;
_isBusy = value;
RaisePropertyChanged();
}
}
The same view, ReportView.xaml, which contains the button calling the above command also contains the following Extended WPF Toolkit Busy Indicator:
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="MasterListViewTemplate">
<view:MasterListView />
</DataTemplate>
</UserControl.Resources>
<xctk:BusyIndicator IsBusy="{Binding IsBusy}">
<StackPanel>
<!-- Other XAML here -->
<ContentControl ContentTemplate="{StaticResource MasterListViewTemplate}" />
</StackPanel>
</xctk:BusyIndicator>
</UserControl>
Then in the MasterListView.cs code-behind, we have this:
public partial class MasterListView : UserControl
{
public MasterListView()
{
InitializeComponent();
Messenger.Default.Register<bool>(this, "RunMasterListReport", RunMasterListReport);
}
public async void RunMasterListReport(bool val)
{
await Task.Run(() =>
{
var dataSet = new DrugComplianceDataSet();
dataSet.BeginInit();
ReportViewer.ProcessingMode = ProcessingMode.Local;
ReportViewer.LocalReport.ShowDetailedSubreportMessages = true;
ReportViewer.LocalReport.DataSources.Clear();
var dataSource = new ReportDataSource
{
Name = "MasterListRandomDataSet",
Value = dataSet.MasterListRandom
};
ReportViewer.LocalReport.DataSources.Add(dataSource);
ReportViewer.LocalReport.ReportEmbeddedResource = "MasterListRandom.rdlc";
dataSet.EndInit();
var adapter = new MasterListRandomTableAdapter { ClearBeforeFill = true }
.Fill(dataSet.MasterListRandom);
Dispatcher.Invoke((MethodInvoker)(() => { ReportViewer.RefreshReport(); }));
});
}
}
However, the busy indicator doesn't trigger, though the report does show after 5 seconds or so. What am I doing wrong? Thanks.
You got a whole soup of wat in there. Things like
await Task.Run(() =>
suggest you don't really understand how async/await works. I'd step down and find some nice documentation to read. Also, you appear to be doing all your work on the UI thread.
Dispatcher.Invoke((MethodInvoker)(() => { ReportViewer.RefreshReport(); }));
You shouldn't be touching a dispatcher in a background worker unless you're updating the UI. It appears you're offloading the actual work you intend to do in a background thread (refreshing the report) on the UI thread.
Maybe your report viewer HAS to run in the UI thread. If that's so (maybe it was designed a while ago and doesn't take advantage of multitasking) there isn't much you can do about this situation. Your UI will be locked while it's processing.
If all your long-running work has to run on the UI thread, then strip out all that nonsense. Before kicking off your work, update IsBusy, then offload execution of ReportViewer.RefreshReport() onto the Dispatcher, but using a low priority DispatcherPriority, so that it runs after the UI updates and shows that it is busy. Your UI will be frozen during processing, but at least you'll give the user an indication of what's going on.
I want a loading indicator to start immediately before the execution of a method. The execution of the method involves the work of entity framework so I don't (can't) put that type of code in a new thread bc entity framework isn't thread safe. So basically in the method below, I want the first line to execute and have the UI update and then come back and execute the rest of the code. Any ideas?
public async void LoadWizard()
{
IsLoading = true; //Need the UI to update immediately
//Now lets run the rest (This may take a couple seconds)
StartWizard();
Refresh();
}
I can't do this:
public async void LoadWizard()
{
IsLoading = true; //Need the UI to update immediately
await Task.Factory.StartNew(() =>
{
//Now lets run the rest (This may take a couple seconds)
StartWizard();
Refresh(); //Load from entityframework
});
//This isn't good to do entityframework in another thread. It breaks.
}
You can invoke empty delegate on UI dispatcher with priority set to Render, so that UI process all the queued operations with equal or higher priority than Render. (UI redraws on Render dispatcher priority)
public async void LoadWizard()
{
IsLoading = true; //Need the UI to update immediately
App.Current.Dispatcher.Invoke((Action)(() => { }), DispatcherPriority.Render);
//Now lets run the rest (This may take a couple seconds)
StartWizard();
Refresh();
}
Assuming your busy indicator visibility is bound to IsLoading property, you are doing "something" wrong in StartWizard or Refresh method. Your StartWizard and Refresh methods should only load data from your data source. You must not have any code that changes the state of UI in your loading methods. Here is some pseudocode..
public async void LoadWizard()
{
IsLoading = true;
StartWizard();
var efData = Refresh();
IsLoading = false;
//update values of properties bound to the view
PropertyBoundToView1 = efData.Prop1;
PropertyBoundToView2 = efData.Prop2;
}
public void StartWizard()
{
//do something with data that are not bound to the view
}
public MyData Refresh()
{
return context.Set<MyData>().FirstOrDefault();
}
I'm working on a WPF (MVVM) app.
Using 2 buttons, one to load data from db and another one to delete a selected item.
When clicking the Load button, a LoadCommand is fired and calls the StartLoadingThread
private void StartLoadingThread()
{
ShowLoadProcessing(); // show some text on top of screen ("Loading in progress...")
ThreadStart ts = delegate
{
LoadMyitems();
System.Windows.Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (EventHandler)
delegate
{
HideLoadProcessing(); // hide the text "Loading in progress..."
}, null, null);
};
ts.BeginInvoke(ts.EndInvoke, null);
}
Works fine, now when I select an item and click the Delete button, the DeleteItemCommand is fired and calls the StartDeletingThread
private void StartDeletingThread()
{
ShowDeleteProcessing(); // Show on top of screen "Deleting in progress..."
ThreadStart ts = delegate
{
DeleteSelectedItem();
System.Windows.Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (EventHandler)
delegate
{
HideDeletingProcessing();
}, null, null);
};
ts.BeginInvoke(ts.EndInvoke, null);
}
When StartDeletingThread is started, I'm getting the following exception:
{"The calling thread must be STA, because many UI components require this."}
I think you want something like this, though I am a bit newbie in WPF, you will have to replace this.BeginInvoke with something what is doing same in WPF (Dispatcher). Also code may not compile (add Action type conversion for Thread?), but idea is to simply start thread (you invoke it, for some reasons, why?) and in that thread invoke UI operation after deleting.
private void StartDeletingThread()
{
ShowDeleteProcessing(); // Show on top of screen "Deleting in progress..."
new Thread(() =>
{
DeleteSelectedItem();
this.BeginInvoke(() => HideDeletingProcessing());
}.Start();
}
Try invoking StartDeletingThread using the application dispatcher like this:
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,new
Action(()=>StartDeletingThread())
private void StartDeletingThread()
{
ShowDeleteProcessing(); // Show on top of screen "Deleting in progress..."
Task.Run(() =>
{
DeleteSelectedItem();
Application.Current.Dispatcher.BeginInvoke((Action)HideDeletingProcessing);
});
}
probably will blow up at DeleteSelectedItem();. I know we all love to show progress bars for actions that take less than a second, but why bother when it leads to questions on StackOverflow?
private void StartDeletingThread()
{
DeleteSelectedItem();
}
Done and done. I seriously doubt it takes a long time to delete the selected item.
If, in some rare case it does... then you need to find out in DeleteSelectedItem where the UI is getting touched, and use the application's dispatcher to do the touching.
(Side note, here's how you'd safely multithread in 4.5, using async/await... safe, as long as you understand the repercussions of async void, that is)
private async void StartDeletingThread()
{
// we're in the UI thread
ShowDeleteProcessing();
await DeleteSelectedItem();
// back in the UI thread
HideDeletingProcessing();
}
private Task DeleteSelectedItem()
{
// doing the work on a Task thread
return Task.Run(() => DeleteSelectedItem = null);
}