I have a Window that only has one frame to show the different pages that my application has.
When a Page loads, it initializes several threads that deal with different elements of the UI. When I change the page with frame.Navigate(new NextPage()) I abort those threads, as it doesn't make sense to keep them running when the page is not in the foreground.
The problem comes when I make frame.GoBack(), since I have no way to relaunch those treads on the page because no code is executed when I return to the page.
There is something similar to the OnPause, OnStart, OnStop method of Android but for WPF and pages?
Something like the following:
public MainPage()
{
InitializeComponent();
var thread = new Thread(() => Code());
thread.Start();
}
public Code()
{
// Do Stuff
}
public Continue()
{
thread.abort()
frame.Navigate(new NextPage())
}
public OnPageReloaded()
{
thread.start()
}
You should look into the Task Parallel Library (TPL) and create tasks instead of threads. Calling Abort() on a thread is bad practice.
As to your actual question, you could initialize your work in the Loaded event handler of the Page instead of doing it in the constructor:
public partial class Page1 : Page
{
public Page1()
{
InitializeComponent();
Loaded += Page1_Loaded;
}
private void Page1_Loaded(object sender, RoutedEventArgs e)
{
//start your tasks here...
}
}
The Loaded event will be invoked each time your Page instance is being navigated to. There is also an Unloaded event that you can use to do cleanup.
Related
I'm working on a Xamarin.Forms project that supports iOS and Android devices, and I'm using the MVVM design pattern.
I have navigation root page that consists of a ListView, when item is selected on this ListView, I execute the following command to Navigate to item details view.
Page DetailsPage = new View.DetailsView(SelectedItemData);
await Navigation.PushAsync(DetailsPage);
Once this Details Page is opened, I start running a background task.
private void StartBackgroundTask(){
TimerBackgroundTask = new Timer((o) => {
Device.BeginInvokeOnMainThread(() => Update()); }, null, 0, 1000);
}
}
Which is based on this class
public class Timer : CancellationTokenSource
{
public bool IsDisposed { get; set; }
public Timer(Action<object> callback, object state, int dueTime, int period)
{
System.Threading.Tasks.Task.Delay(dueTime, Token).ContinueWith(async (t, s) =>
{
Tuple<Action<object>, object> tuple = (Tuple<Action<object>, object>)s;
while (!IsCancellationRequested)
{
await System.Threading.Tasks.Task.Run(() => tuple.Item1(tuple.Item2));
await System.Threading.Tasks.Task.Delay(period);
}
},
Tuple.Create(callback, state), CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default);
}
protected override void Dispose(bool disposing)
{
IsDisposed = true;
if (disposing)
{
Cancel();
}
base.Dispose(disposing);
}
}
Update function updates UI every 1 second.
Everything works fine and as it should, no issues here, however problems start to occur once I navigate back to root page, and back to details page - doing so twice causes the following error:
System.ArgumentException'jobject' must not be IntPtr.Zero. Parameter name: jobject
The problem stops occurring once the StartBackgroundTask gets disabled entirely from the code, so I believe that it is the one responsible for the error. Furthermore, I'm fairly convinced that this background task keeps on running somewhere in the thread even though I navigate back to the root page and I believe that if I could somehow dispose of the background task OnDissapearing event / navigation back button pressed, the error would no longer persist.
Unfortunately I have no idea how I how or even if its possible to somehow bind command to navigation back pressed event given my Views are bound to ViewModel.
Any tips would be greatly appreciated.
You can detect that a page is being dismissed by overriding OnDisappearing. In your DetailPage you could have something like this:
protected override void OnDisappearing()
{
TimerBackgroundTask?.Dispose();
base.OnDisappearing();
}
In my XAML UI I have a listview which contains a list of complex objects. These complex objects have an async initialization method which loads in the data (downloads an image, formats the text, etc).
Here's the setup (pseudo code):
public class PageViewModel
{
public ObservableCollection<ItemViewModel> Items;
public async Task InitializeAsync()
{
var models = await GetModelsAsync();
List<Task> initTasks = new List<Task>();
foreach(var model in models)
{
var vm = new ItemViewModel(model)
initTasks.Add(vm.InitializeAsync());
Items.Add(vm);
}
await Task.WhenAll(initTasks);
}
}
The issue i'm seeing is that it seems like the UI thread is being blocked and unresponsive until all the tasks have completed which is confusing me. All my async complex logic is in an awaitable task.
This lead me to experiment with the only other logic here, the creation of the view model. The issue seems to dissapear when I wrap the following code in a Task.Run:
var vm = Task.Run(() => new ItemViewModel(item))
This surprised me because there's very little to no logic in the ViewModel constructor which is why I was fine initially putting it on the UI thread.
Does anyone have thoughts on why I would see the UI thread block here? Do you have any code suggestions?
I can't be sure where you are calling your init() but you should be (unless you use an intelligent framework that automatically calls it for you) callingit from your Page OnNavTo override, like this:
public sealed partial class MainPage : Page
{
public MainPage() { InitializeComponent(); }
MainPageViewModel ViewModel => DataContext as MainPage;
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
DataContext = new MainPageViewModel();
await DataContext.InitAsync();
}
}
public class MainPageViewModel
{
public Task InitAsync()
{
// TODO
}
}
As an aside, I don't recommend you load your models in parallel, but instead in series because of performance on low-powered devices. Make sense? I should not make a noticeable difference to your user, bur your app will not lock if the generation is costly.
Best of luck!
I have an application that involves a database. Previously, upon opening a window, I would query the database and use this to populate aspects of my view model. This worked reasonably well, but could create noticeable pauses when the data access took longer than expected.
The natural solution, of course, is to run the database query asynchronously and then populate the view model when that query completes. This isn't too hard, but it raises some interesting questions regarding error handling.
Previously, if something went wrong with the database query (a pretty big problem, granted), I would propagate the exception through the view model constructor, ultimately making it back up to the caller that wanted to open the window. It could then display an appropriate error and not actually open the window.
Now, however, the window opens right away, then populates later as the query completes. The question, now, is at what point should I check for an error in the background task? The window is already open, so the behavior needs to be different somehow, but what is a clean way to indicate the failure to the user and allow for graceful recovery/shutdown?
For reference, here is a snippet demonstrating the basic pattern:
public ViewModel()
{
_initTask = InitAsync();
//Now where do I check on the status of the init task?
}
private async Task InitAsync()
{
//Do stuff...
}
//....
public void ShowWindow()
{
var vm = new ViewModel(); //Previously this could throw an exception that would prevent window from being shown
_windowServices.Show(vm);
}
One option I've considered is use an asynchronous factory method for constructing the ViewModel, allowing the entire thing to be constructed and initialized before attempting to display the window. This preserves the old approach of reporting errors before the window is ever opened. However, it gives up some of the UI responsiveness gained by this approach, which allows initial loading of the window to occur in parallel with the query and also allows me (in some cases) to update the UI in increments as each query completes, rather than having the UI compose itself all at once. It avoids locking up the UI thread, but it doesn't reduce the time before the user actually sees the window and can start interacting with it.
Maybe use some kind of messaging/mediator between your viewmodel and underlying service?
Semi-pseudo code using MVVMLight
public ViewModel()
{
Messenger.Default.Register<NotificationMessage<Exception>>(this, message =>
{
// Handle here
});
Task.Factory.StartNew(() => FetchData());
}
public async Task FetchData()
{
// Some magic happens here
try
{
Thread.Sleep(2000);
throw new ArgumentException();
}
catch (Exception e)
{
Messenger.Default.Send(new NotificationMessage<Exception>(this, e, "Aw snap!"));
}
}
I dealt with a similar problem here. I found it'd be best for me to raise an error event from inside the task, like this:
// ViewModel
public class TaskFailedEventArgs: EventArgs
{
public Exception Exception { get; private set; }
public bool Handled { get; set; }
public TaskFailedEventArgs(Exception ex) { this.Exception = ex; }
}
public event EventHandler<TaskFailedEventArgs> TaskFailed = delegate { };
public ViewModel()
{
this.TaskFailed += (s, e) =>
{
// handle it, e.g.: retry, report or set a property
MessageBox.Show(e.Exception.Message);
e.Handled = true;
};
_initTask = InitAsync();
//Now where do I check on the status of the init task?
}
private async Task InitAsync()
{
try
{
// do the async work
}
catch (Exception ex)
{
var args = new TaskFailedEventArgs(ex);
this.TaskFailed(this, args);
if (!args.Handled)
throw;
}
}
// application
public void ShowWindow()
{
var vm = new ViewModel(); //Previously this could throw an exception that would prevent window from being shown
_windowServices.Show(vm);
}
The window still shows up, but it should be displaying some kind of progress notifications (e.g. using IProgress<T> pattern), until the end of the operation (and the error info in case it failed).
Inside the error event handler, you may give the user an option to retry or exit the app gracefully, depending on your business logic.
Stephen Cleary has a series of posts on his blog about Async OOP. In particular, about constructors.
i'm currently coding C# app for Windows Store.
I have Cache class, News UserControl class and MainPage class
I'm calling in MainPage constructor Cache class and then call InitializeData for News class where i using data from Cache, but there is problem, in Cache constructor i receiving datas but he didnt do whole function, he switching from Cache constructor to InitializeData at third await function.
MainPage:
public MainPage()
{
this.InitializeComponent();
Cache.Cache cache = new Cache.Cache();
NewsContent.InitializeData(cache.MyData);
}
Cache:
public Cache()
{
Initialization = Init();
}
public Task Initialization
{
get;
private set;
}
private async Task Init()
{
try
{
cS = await folder.CreateFileAsync("cache.txt", CreationCollisionOption.OpenIfExists);
cS_titles = await folder.CreateFileAsync("titles_cache.txt", CreationCollisionOption.OpenIfExists);
string contentOfFile = await FileIO.ReadTextAsync(cS);
int contentLength = contentOfFile.Length;
if (contentLength == 0) // download data for first using
{
await debug.Write("Is empty!");
//.......
// ....
await FileIO.AppendTextAsync(cS, file_content);
await FileIO.AppendTextAsync(cS_titles, file_content_titles);
}
else // check for same data, if isnt same download new, else nothing
{
await debug.Write(String.Format("Isnt empty. Is long: {0}", contentLength)); // here he break and continue to NewsContent.InitializeData(cache.MyData);
// ....
// ....
}
await MyFunction(); // i need get constructor to this point then he will do NewsContent.InitializeData(cache.MyData);
}
catch (Exception)
{
}
}
Is this possible to do it? For any idea thank you!
Stephen Cleary's article on async and constructors describes how to make this work.
In your case, I think the factory pattern (as suggested in Jon's answer) won't work for MainPage, because it's a GUI component. But the second approach, The Asynchronous Initialization Pattern, will work.
You already implemented that pattern for Cache, now you also need to implement it for MainPage:
public MainPage()
{
Initialization = InitializeAsync();
}
public Task Initialization { get; private set; }
private async Task InitializeAsync()
{
Cache.Cache cache = new Cache.Cache();
await cache.Initialization;
NewsContent.InitializeData(cache.MyData);
}
If MainPage has some events that depend on the initialization being complete, you can make then async and add await this.Initialization at their beginning. Also, you might want to enable buttons or things like that at the end of MainPage's InitializeAsync().
This is what happens when you call an async method and never wait for it to finish, basically.
The whole point of an async method is that you don't block... and your constructor can't be asynchronous itself.
One option would be to write an asynchronous static method to create a cache:
static async Task<Cache> CreateCache()
{
// Change your InitializeData to return the data which the cache needs
var data = await InitializeData();
return new Cache(data);
}
Fundamentally you still need whatever calls CreateCache to understand that it's happening asynchronously though. You don't want to block the UI thread waiting for it all to initialize.
EDIT: I hadn't spotted that this is called from the MainPage constructor. You could potentially apply the same approach again:
public static async Task<MainPage> CreateMainPage()
{
var cache = await Cache.CreateCache();
return new MainPage(cache);
}
This is assuming you really, really can't let the main page be created without the cache being completely initialized. If you could handle that (e.g. showing something like a "Loading..." status until it's finished initializing) then that would be better.
I have a web page (aspx). This page has a button , a UpdatePanel and a Timer. Now my problem is as follow suppose 10 users are on this page at the same time and suppose user number 3 and 8 click its button then all user’s UpdatePanel should get updated. What is right way to achieve this functionality?
Since each user is running its own copy of the web application so whats happenning on one user's browser can't be notified to the other user. One thing you could do is when one user clicks the button to update, you could save it, whereas all the user application could ping the server may be every 2 secs to know if updation happens and if so updates.
You could use a Global property. On that property, you can put an Observer pattern, and let the visitors subscribe. When you alter the Application property ( shared throughout all Sessions ), you call the Notify() method. The client Notify() method gets called, and there you put functionality to update the UpdatePanel.
This code is NOT TESTED, it is a guideline
// *** GLOBAL.ASAX
// This collection will contain all the updatepanels that need to be updated
private List<IUpdatePanelClient> _registeredClients = new List<IUpdatePanelClient>();
public static void RegisterClient(IUpdatePanelClient client)
{
_registeredClients.Add(client);
}
public static void UnregisterClient(IUpdatePanelClient client)
{
_registeredClients.Remove(client);
}
// Which client is triggering the update call ?
private IUpdatePanelClient _clientUpdating = null;
public static IUpdatePanelClient ClientUpdating
{
get { return _clientUpdating ; }
set { _clientUpdating = value; Notify(); }
}
// Notify the clients
public static void Notify()
{
foreach(IUpdatePanelClient client in _registeredClients)
{
client.Update();
}
}
// *** IUPdatePanelClient.CS
interface IUpdatePanelClient // Interface to make the calls
{
void Update();
}
// *** Your codepage
public class MyUpdatePanelPage : Page, IUpdatePanelClient // Inherit the interface
{
public void Page_Load(Object sender, EventArgs e)
{
MyUpdatePanelPage.Global.RegisterClient(this);
}
public void Btn_Click(Object sender, EventArgs e)
{
MyUpdatePanelPage.Global.ClientUpdating = this;
}
public void Update()
{
this._updatePanel1.Update();
}
}
Your question doesn't have enough information for anyone to answer properly. If there is information that you want to keep all users update-to-date on, store that information in a database. When one user edits the data from their user session, whenever other user's get their page refreshed, they will have the most updated data. If you want to have their page refreshed periodically, use a javascript timer.