I have a WPF (UI) app that retrieves data from a SQL database on init/load time.
To make the app's startup time fast (and not freeze due to loading the data) i tried doing a couple of things to load the data asynchronously, but with not much luck in terms of the UX i was expecting.
Basically the UI still freezes (even if it is just for a brief moment).
Here's what i've tried:
The method which the data loads is marked with async Task. I invoke this method like this
// Note that i use ConfigureAwait(true) because the data is being loaded from the UI.
LoadData().CongigureAwait(true);
Here is the method:
private async void LoadData()
{
try
{
using (var context = new DataModel.BusinessData())
{
var people= await context.People
.ToListAsync()
.ConfigureAwait(true);
foreach (var person in people)
{
this.People.Add(new PersonItem(person));
}
}
}
catch (Exception ex)
{
throw;
}
}
Now despite the asynchronous nature of this data retrieval, it is being handled back on the main thread (the UI thread). Again, this is being called from the UI (MVVM pattern), so i need this.
The end result is this:
I still see a brief moment of UI freezing during startup time.
Question:
How can data be retrieved AND be handled back by the UI thread WITHOUT
freezing the UI? Is this even possible when being handled back by the
UI thread?
It's only the actual call to ToListAsync that is asynchronous. The rest of your code executes on the UI thread.
If People contains a lot of items, rendering them may be slow.
You could try to assing the People property to a new collection instead of adding the items to the data-bound collection one by one:
private async Task LoadData()
{
using (var context = new DataModel.BusinessData())
{
var people = await context.People.ToListAsync();
this.People = await Task.Run(() => new ObservableCollection(people.Select(person => new PersonItem(person))));
}
}
Remember to implement INotifyPropertyChanged and raise the PropertyChanged event for the People property.
You could also try to execute all code on a background thread in case the implementation of ToListAsync actually blocks:
private async Task LoadData()
{
await Task.Run(() =>
{
using (var context = new DataModel.BusinessData())
{
this.People = new ObservableCollection(context.People.Select(person => new PersonItem(person)).ToArray());
}
});
}
If it's still slow, you need to either decrease the number of items to display or use a faster component to render the items.
Related
I have one Async method which performs different operations.
public async void OnClickPublish( )
{
Loader loader = new Loader();
loader.Show();
await Task.Run(() => PublishSlides(loader));
}
private async Task PublishSlides(Loader loader)
{
await loader.Dispatcher.Invoke(async () =>
{
loader.LoaderMessage("Opration 1 start..");
List<SlideProperties> DBList= await Task.Run(() =>
objSlideImg.DBOpration()); //UI Not needed. work nice
var cloudTask = SendToCloudAsync(DBList);
await cloudTask.ContinueWith(task =>
{
if (task.IsFaulted)
{
loader.LoaderMessage(task.Exception.Message + " problem occur in cloud publish");
return;
}
loader.ShowSuccess("broadcasting..");
}, uiScheduler);
}
}
/*PROBLEM OCCUR IN THIS METHOD*/
public async Task<bool> SendToCloudAsync(List<SlideProperties> DBList)
{
**DashboardUI dashboard= new DashboardUI();** /* giving issue The calling thread must be STA, because many UI components require this.*/
dashboard.ShowDashboard()
}
So when I called the SendToCloudAsync() methods it will give the issue The calling thread must be STA, because many UI components require this. SendToCloudAsync() this method will show the dashboard of my APP.
I strongly recommend separating UI code from background code, and having the UI layer "drive" the business/background code.
Currently, OnClickPublish calls PublishSlides on a background thread (Task.Run), and then PublishSlides immediately jumps back to the UI thread (Dispatcher.Invoke) and runs its entire method body on the UI thread. This isn't accomplishing anything - just jumping back and forth across threads.
To properly divide UI from background thread code, there are two primary techniques and one less common technique.
The first primary technique is to use return values. Instead of having a method retrieve/calculate data and then update the UI with the data results, just have it retrieve/calculate data and return it. Then the calling thread can decide whether the retrieving/calculation should be on a background thread (Task.Run), and then place the data on the UI.
The second primary technique is to use progress updates. Instead of having a "work" method reach into the UI and update the progress directly, it should use IProgress<T> to report progress. Then the calling method can decide whether the "work" should be run on a background thread (Task.Run), and it can pass a progress implementation (e.g., Progress<T>) that performs progress updates on the UI thread.
The less common technique is to proactively update the UI from a continuously running background operation. I recommend using SynchronizationContext for that, but because the code is more complex and doesn't apply to this question I'll just stop there.
Here's one example of what your code could look like using return values and await. The exact details will depend on the details of the rest of your code:
public async void OnClickPublish()
{
Loader loader = new Loader();
loader.Show();
PublishSlides(loader);
}
private async Task PublishSlides(Loader loader)
{
loader.LoaderMessage("Opration 1 start..");
List<SlideProperties> DBList = await Task.Run(() => objSlideImg.DBOpration());
try
{
await SendToCloudAsync(DBList);
loader.ShowSuccess("broadcasting..");
}
catch (Exception ex)
{
loader.LoaderMessage(ex.Message + " problem occur in cloud publish");
}
DashboardUI dashboard = new DashboardUI();
dashboard.ShowDashboard();
}
public async Task<bool> SendToCloudAsync(List<SlideProperties> DBList)
{
...
}
I want to listen to a change stream for a collection, without blocking the thread.
In the code below the ForEachAsync will block forever, processing changes as they occur.
using (var cursor = await collection.WatchAsync())
{
await cursor.ForEachAsync(change =>
{
// process change event
});
}
I have tried removing "await" and storing the task in a variable instead, but no changes will be processed before i actually either await or .Wait() the task later.
I would expect that even though I don't await the task, the events would still be processed.
What can I do, so my entire program doesn't block to listen for changes?
I am considering wrapping the functionality in a new thread, but is that really necessary?
I don't have enough reputation to comment (yet) so my feedback in the form of an answer:
You should be working with events (listeners, handlers) instead of await/async in this situation.
It's difficult to elaborate on this without having more information on what you're trying to accomplish.
So, I figured out that the following works...
static void Main(string[] args)
{
Task watchTask = WatchCollection();
// Do other stuff here, while watching...
}
private static async Task WatchCollection()
{
using (var cursor = collection.Watch())
{
await cursor.ForEachAsync(change =>
{
// process change event
});
}
}
While I was attempting to do it like this, which doesn't work:
static void Main(string[] args)
{
using (var cursor = collection.Watch())
{
Task watchTask cursor.ForEachAsync(change =>
{
// process change event
});
}
// Do other stuff here, while watching
}
So, the actual ForEachAsync task is awaited, while the outer Task which wraps the entire functionality is not awaited... I am either too tired or not familiar enough with Tasks to give a good explanation of why one works and the other doesn't.
I am building a screen for my app in xamarin.forms the caul is based on a tabbedpage which is built dynamically based on a list of objects which I get as a result of consuming a service.
After I call the method to consume the API that brings the list, I need to go through it based on certain data of it to fill an observable collection of viewmodels, which will be the tabs. The problem I have is that I do not know how to call the async method that consumes the API in a synchronized way so that the consumption of the API does not conflict with the operation of going through the list.
Then a fraction of the code of my ViewModel:
public MonitoringViewModel()
{
LoadThings();
Tabs = new ObservableCollection<MonitoringTabsViewModel>();
foreach (PcThing t in Things)
{
Tabs.Add(new MonitoringTabsViewModel(t.description));
}
}
private async void LoadThings()
{
Things = new List<PcThing>(await App.WebApiManager.GetCustomerThinksAsync());
}
What I get is that in xamarin live player the app after a few seconds go from the green signal to the red one without showing anything, and in the log of it I get this:
Target of GetEnumerator is null (NullReferenceException)
Since you are doing this in the constructor , I would try the following:
using System.Threading.Tasks;
The risk here is if you are not in control of the LoadThings completing, it can hang.
public MonitoringViewModel()
{
var task = Task.Run(async () => { await LoadThings();}
Task.WaitAll(task); //block and wait for task to complete
public async Task<List<PcThing>> LoadThings()
{
return await App.WebApiManager.GetCustomerThinksAsync();
}
And in your ViewModel
Things = LoadThings().GetAwaiter().GetResult();
I'm trying to transition from the Event-based Asynchronous Pattern where I tracked running methods using unique id's and the asynoperationmanager. As this has now been dropped from Windows 8 Apps I'm trying to get a similar effect with Async/Await but can't quite figure out how.
What I'm trying to achieve is something like
private async Task updateSomething()
{
if(***the method is already running***)
{
runagain = true;
}
else
{
await someMethod();
if (runagain)
{
run the method again
}
}
}
The part I'm struggling with is finding out if the method is running. I've tried creating a Task and looking at the status of both that and the .status of the async method but they don't appear to be the correct place to look.
Thanks
UPDATE: This is the current code I use in .net 4 to achieve the same result. _updateMetaDataAsync is a class based on the Event-Based Asynchronous Pattern.
private void updateMetaData()
{
if (_updateMetaDataAsync.IsTaskRunning(_updateMetaDataGuid_CheckAllFiles))
{
_updateMetaDataGuid_CheckAllFiles_Again = true;
}
else
{
_updateMetaDataGuid_CheckAllFiles_Again = false;
_updateMetaDataAsync.UpdateMetaDataAsync(_updateMetaDataGuid_CheckAllFiles);
}
}
private void updateMetaDataCompleted(object sender, UpdateMetaDataCompletedEventArgs e)
{
if (_updateMetaDataGuid_CheckAllFiles_Again)
{
updateMetaData();
}
}
async/await itself is intended to be used to create sequential operations executed asynchronously from the UI thread. You can get it to do parallel operations, but generally the operations "join" back to the UI thread with some sort of result. (there's also the possibility of doing "fire-and-forget" types of asynchronous operations with await but it's not recommended). i.e. there's nothing inherent to async/await to support progress reporting.
You can get progress out of code using async/await; but you need to use new progress interfaces like IProgress<T>. For more info on progress reporting with async/await, see http://blogs.msdn.com/b/dotnet/archive/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx. Migrating to this should just be a matter of calling an IProgress delegate instead of a Progress event.
If you're using a Task you've created, you can check the Task's Status property (or just see Task.IsCompleted if completion is the only state you are interested in).
That being said, await will not "return" until the operation either completes, raises an exception, or cancels. You can basically safely assume that, if you're still waiting on the "await", your task hasn't completed.
SemaphoreSlim queueToAccessQueue = new SemaphoreSlim(1);
object queueLock = new object();
long queuedRequests = 0;
Task _loadingTask;
public void RetrieveItems() {
lock (queueLock) {
queuedRequests++;
if (queuedRequests == 1) { // 1 is the minimum size of the queue before another instance is queued
_loadingTask = _loadingTask?.ContinueWith(async () => {
RunTheMethodAgain();
await queueToAccessQueue.WaitAsync();
queuedRequests = 0; // indicates that the queue has been cleared;
queueToAccessQueue.Release()
}) ?? Task.Run(async () => {
RunTheMethodAgain();
await queueToAccessQueue.WaitAsync();
queuedRequests = 0; // indicates that the queue has been cleared;
queueToAccessQueue.Release();
});
}
}
}
public void RunTheMethodAgain() {
** run the method again **
}
The added bonus is that you can see how many items are sitting in the queue!
With the code below, the final UI updates made in the final ContinueWith never take place. I think it is because of the Wait() I have at the end.
The reason I am doing that is because without the Wait, the method will return the IDataProvider before its finished being constructed in the background.
Can someone help me get this right?
Cheers,
Berryl
private IDataProvider _buildSQLiteProvider()
{
IDataProvider resultingDataProvider = null;
ISession session = null;
var watch = Stopwatch.StartNew();
var uiContext = TaskScheduler.FromCurrentSynchronizationContext();
// get the data
var buildProvider = Task.Factory
.StartNew(
() =>
{
// code to build it
});
// show some progress if we haven't finished
buildProvider.ContinueWith(
taskResult =>
{
// show we are making progress;
},
CancellationToken.None, TaskContinuationOptions.None, uiContext);
// we have data: reflect completed status in ui
buildProvider.ContinueWith(
dataProvider =>
{
// show we are finished;
},
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiContext);
try {
buildProvider.Wait();
}
catch (AggregateException ae)
{
foreach (var e in ae.InnerExceptions)
Console.WriteLine(e.Message);
}
Console.WriteLine("Exception handled. Let's move on.");
CurrentSessionContext.Bind(session);
return resultingDataProvider;
}
====
just to be clear
I am not having trouble talking to the ui thread. The first continue with updates the ui just fine. The trouble I am having is the timing of the last ui update and the return of the data provider.
I commented out some of the code to reduce the noise level in tis post and focus on the task sequencing.
====
ok, working code
private void _showSQLiteProjecPicker()
{
var watch = Stopwatch.StartNew();
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
ISession session = null;
// get the data
var buildProvider = Task.Factory.StartNew(
() =>
{
var setProgress = Task.Factory.StartNew(
() =>
{
IsBusy = true;
Status = string.Format("Fetching data...");
},
CancellationToken.None, TaskCreationOptions.None, uiScheduler);
var provider = new SQLiteDataProvider();
session = SQLiteDataProvider.Session;
return provider;
});
buildProvider.ContinueWith(
buildTask =>
{
if(buildTask.Exception != null) {
Console.WriteLine(buildTask.Exception);
}
else {
Check.RequireNotNull(buildTask.Result);
Check.RequireNotNull(session);
_updateUiTaskIsComplete(watch);
CurrentSessionContext.Bind(session);
var provider = buildTask.Result;
var dao = provider.GetActivitySubjectDao();
var vm = new ProjectPickerViewModel(dao);
_showPicker(vm);
}
},
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiScheduler);
}
UPDATE BELOW
This code doesn't look like it warrants TPL to me. Looks like maybe a good use for a BackgroundWorker instead!
Either way, the updates are probably not taking place because you can't update the UI from a separate thread -- you need to run the update on the UI thread. You should use the Dispatcher for this (http://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms contains info for both WPF and WinForms)
Update:
So I obviously missed some of the code so here's a revised answer. First of all, Nicholas is correct -- .ContinueWith returns a new task (http://msdn.microsoft.com/en-us/library/dd270696.aspx). So instead of
var result = Task.Factory.StartNew(...);
result.ContinueWith(...);
you probably want to create a new task and then make all the ContinueWith() calls and assign to the task and then call .Start() on the task. Something like:
var task = new Task(...).ContinueWith(...);
task.Start();
However, there is a flaw in the design to begin with (as I see it)! You're trying to run this code async, wihch is why you're using threads and TPL. However, you're calling buildProvider.Wait(); on the UI thread which blocks the UI thread until this task completes! Aside from the issue of repainting the UI in the ContinueWith() while the UI thread is blocked, there's no benefit to multithreading here since you're blocking the UI thread (a major no-no). What you probably want to do is stick the Bind()-ing inside a ContinueWith or something so that you don't have to call Wait() and block the UI thread.
My $0.02 is that if you expect the query to take a long time what you really want is 2 threads (or tasks in TPL)-- one to perform the query and one to update the UI at intervals with status. If you don't expect it to take so long I think you just want a single thread (Task) to query and then update the UI when it's done. I would probably do this via BackgroundWorker. TPL was built for managing lots of tasks and continuations and such but seems overkill for this kind of thing -- I think you could do it using a BackgroundWorker in a lot less code. But you mention you want to use TPL which is fine, but you're going to have to rework this a bit so that it actually runs in the background!
PS - you probably meant to put the Console.WriteLine("Exception handled. Let's move on."); inside the catch
I'm a little hazy, but last time I used the TPL I found it confusing. ContinueWith() returns a new Task instance. So you need to assign the second ContinueWith() result to a new variable, say var continuedTask = builderProvider.ContinueWith(...), and then change the last one to reference continuedTask.ContinueWith() instead of buildProvider.ContinueWith(). Then Wait() on the last Task.
Hope that helps!