Garbage collecting issue with Custom viewbinding in mono touch and mvvmcross - xamarin.ios

I have a custom calendar control for which there is an custom viewbinding. In this viewbinding we hook up some events which are not decoupled correct and therefor is the garbage collecting not completed. In the following is our custom view binding. As you can see the event is hooked up in the constructor and decoupled in the OnSelectedDate event is triggered(the user selects an date). Therefore if you choose a date the event is decouple correct and garbage collected but if you just go back, the event is still hooked up and no garbage collecting is performed. I thought about trigger the event with null values and and thereby decoulpe the event. But I think there must be some more clever way to achieve this.
namespace CmsApp.Core.Binders
{
public class CalendarViewBinding:MvxBaseTargetBinding
{
private CalendarView _calendarView;
private DateTime _currentValue;
public CalendarViewBinding(CalendarView calendarView)
{
_calendarView = calendarView;
_calendarView.OnDateSelected+=OnDateSelected;
}
protected override void Dispose(bool isDisposing)
{
if(_calendarView!=null)
{
_calendarView.OnDateSelected -= OnDateSelected;
_calendarView = null;
}
base.Dispose(isDisposing);
}
private void OnDateSelected(object sender, SelectedDateEventArgs args)
{
_currentValue = args.SelectedDate;
this.FireValueChanged(_currentValue);
_calendarView.OnDateSelected -= OnDateSelected;
}
public override void SetValue(object value)
{
var date = (DateTime)value;
_currentValue = date;
_calendarView.SelectedDate = _currentValue;
}
public override Type TargetType
{
get
{
return typeof(DateTime);
}
}
public override MvxBindingMode DefaultMode
{
get
{
return MvxBindingMode.TwoWay;
}
}
}
}
Any help is appreciated :)

It looks to me like your binding is almost correct.
The only issue I can see is that it unsubscribes from the event too often - you can't call _calendarView.OnDateSelected -= OnDateSelected; twice - but I don't think this is the problem you are seeing.
I currently would guess that the problem is not in the code you are using:
either there's a bug in the binding code in the underlying framework you are using
or something is a bug/issue in the way you are using this binding
or your memory leak has nothing to do with this binding
It's not easy to test this from the limited code posted here, but it would be simpler if you could produce a simple app that reproduces the leak you are seeing. Share that and you might be able to get more feedback.
If you believe my guesses are wrong, then the only thing I can suggest is that you switch to WeakReferences inside your binding - but this feels like a sticking plaster rather than a cure.
Just adding a link to when to release objects in mono touch / mvvmcross

Related

Cross-thread issue not resolved despite the usage of Invoke()

My C# app has lots of forms to perform various tasks. To keep things simple, I have a static class FormsCollection where the instances of each of the other forms are kept, so that they are shown and hidden from one place.
Now i was getting that classic debug time "Cross-thread" error. I tried to fix it the following way:
public class FormsCollection : Control // inherited from Control only to be
// able to call "this.Invoke"
{
public delegate void ShowFormDelegate(Form form);
public static Main mainForm;
// and many other forms...
public void ShowForm(Form form)
{
if (form.InvokeRequired)
{
ShowFormDelegate delegateFunc = new ShowFormDelegate(ShowForm);
this.Invoke(delegateFunc, new object[] { form });
}
else
{
previousForm = currentForm;
currentForm.Hide();
currentForm = form;
currentForm.Show();
}
}
}
Inside the user/caller forms, i simply make an object of FormsCollection and call the ShowForm method (almost a 100 such calls):
FormsCollection f = new FormsCollection();
f.ShowForm(FormsCollection.mainForm);
And after all this ordeal, what i get is that the same error appears at the very same spot as before! What an irony! :)
What am i doing wrong? Please help me out....
I got extremely hard-to-find answer to this problem from this slightly irrelevant page. Following is the updated code for any struggling programmers looking for a work around to this problem:
public void Show(Form nextForm)
{
Thread thread = new Thread(
new ThreadStart(() =>
{
PreviousForm.BeginInvoke(
new Action(() =>
{
PreviousForm = CurrentForm;
CurrentForm = nextForm;
PreviousForm.Hide();
CurrentForm.Show();
}
));
}
));
thread.Start();
}
This seems to have resolved the cross-thread problem that i was facing despite several work-arounds. It is working as yet without raising any exceptions and I have tested almost all the scenarios of my app going back and forth from form to form.

How can I execute code from the Release / Release All buttons in the Release AR Documents screen

I've got a customization to the Invoice & Memo screen where I execute some custom code (web service calls) when the Release action is activated. This works fine - I knew how to replace the PXAction code and proceeded from there. Now I want to use the Release AR Documents processing screen to do the same thing, but I'm having trouble understanding where / what to override, or where to place my code.
I see the ARDocumentRelease graph constructor with the SetProcessDelegate in the source code, but I'm not sure how to proceed - whether this is where I need to be looking or not. I need to execute my code for each line being released, using the RefNbr in my code.
Since it's an static method, you can't override it. Also, you can't do like it's done in the T300, because you are in processing graph and you can't override the release button with your own. I was able to achieve it by passing callback for each AR document that have been processed.
You can call the Initialize method of the ARDocumentRelease graph to override the logic like you said. After you just have to call ReleaseDoc that uses a callback parameter instead of using the default one.
Here's the code that I came with:
public class ARDocumentRelease_Extension : PXGraphExtension<ARDocumentRelease>
{
public override void Initialize()
{
ARSetup setup = Base.arsetup.Current;
Base.ARDocumentList.SetProcessDelegate(
delegate (List<BalancedARDocument> list)
{
List<ARRegister> newlist = new List<ARRegister>(list.Count);
foreach (BalancedARDocument doc in list)
{
newlist.Add(doc);
}
AddAdditionalLogicToRelease(newlist);
}
);
Base.ARDocumentList.SetProcessCaption("Release");
Base.ARDocumentList.SetProcessAllCaption("Release All");
}
public delegate void PostPorcessing(ARRegister ardoc, bool isAborted);
private void AddAdditionalLogicToRelease(List<ARRegister> newlist)
{
ARDocumentRelease.ReleaseDoc(newlist, true, null, delegate(ARRegister ardoc, bool isAborted) {
//Add your logic to handle each document
//Test to check if it was not aborted
});
}
}
Please note that you must always call static methods from within long running process and create necessary objects there.
Processing delegate logic is implemented as long running process which creates worker thread to execute the processing logic.
You have AddAdditionalLogicToRelease() method which requires object instance in order to call and will fail during thread context switches and hence the issue. So, you must have create object instance inside the thread context and then call instance method.
In general, method that gets called from long running processes are declared static and required objects/graphs are created inside this static method to do some work. See below example how to properly override ARDocumentRelease graph for this purpose:
public class ARDocumentRelease_Extension : PXGraphExtension<ARDocumentRelease>
{
public override void Initialize()
{
Base.ARDocumentList.SetProcessDelegate(
delegate (List<BalancedARDocument> list)
{
List<ARRegister> newlist = new List<ARRegister>(list.Count);
foreach (BalancedARDocument doc in list)
{
newlist.Add(doc);
}
// use override that allows to specify onsuccess routine
ARDocumentRelease.ReleaseDoc(newlist, true, null, (ardoc, isAborted) =>
{
//Custom code here, such as create your GL
});
}
);
}
}
I think it's the function
public static void ReleaseDoc(List<ARRegister> list, bool isMassProcess, List<Batch> externalPostList, ARMassProcessDelegate onsuccess)
under ARDocumentRelease businesss logic.

Binding a ReactiveCommand prevents a ViewModel from being garbage collected

When I bind a "back button" to a the router in ReactiveUI, my ViewModel is no longer garbage collected (my view too). Is this a bug, or is this me doing something dumb?
Here is my MeetingPageViewModel:
public class MeetingPageViewModel : ReactiveObject, IRoutableViewModel
{
public MeetingPageViewModel(IScreen hs, IMeetingRef mRef)
{
HostScreen = hs;
}
public IScreen HostScreen { get; private set; }
public string UrlPathSegment
{
get { return "/meeting"; }
}
}
Here is my MeetingPage.xaml.cs file:
public sealed partial class MeetingPage : Page, IViewFor<MeetingPageViewModel>
{
public MeetingPage()
{
this.InitializeComponent();
// ** Comment this out and both the View and VM will get garbage collected.
this.BindCommand(ViewModel, x => x.HostScreen.Router.NavigateBack, y => y.backButton);
// Test that goes back right away to make sure the Execute
// wasn't what was causing the problem.
this.Loaded += (s, a) => ViewModel.HostScreen.Router.NavigateBack.Execute(null);
}
public MeetingPageViewModel ViewModel
{
get { return (MeetingPageViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(MeetingPageViewModel), typeof(MeetingPage), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (MeetingPageViewModel)value; }
}
}
I then run, and to see what is up, I use VS 2013 Pro, and turn on the memory analyzer. I also (as a test) put in forced GC collection of all generations and a wait for finalizers. When that line is uncommented above, when all is done, there are three instances of MeetingPage and MeetingPageViewModel. If I remove the BindCommand line, there are no instances.
I was under the impression that these would go away on their own. Is the problem the HostScreen object or the Router that refers to an object that lives longer than this VM? And that pins things down?
If so, what is the recommended away of hooking up the back button? Using Splat and DI? Many thanks!
Following up on the idea I had at the end, I can solve this in the following way. In my App.xaml.cs, I make sure to declare the RoutingState to the dependency injector:
var r = new RoutingState();
Locator.CurrentMutable.RegisterConstant(r, typeof(RoutingState));
then, in the ctor of each view (the .xaml.cs code) with a back button for my Windows Store app, I no longer use the code above, but replace it with:
var router = Locator.Current.GetService<RoutingState>();
backButton.Click += (s, args) => router.NavigateBack.Execute(null);
After doing that I can visit the page as many times as I want and never do I see the instances remaining in the analyzer.
I'll wait to mark this as an answer to give real experts some time to suggest another (better?) approach.

WinForms Thread-safe Controls

I spend a lot of time working with Windows Forms controls but from a background worker thread - I suppose this is good practice really since you don't want your form to be locking up when people click buttons. To be honest, with just about everything GUI related action I normally do in a background worker thread, so the interface is nice an responsive to the user (Wish more people would do that!).
So my question is... every time I have to interact with controls I have to "Invoke" them, with something like:
if (control.InvokeRequired)
{
//
}
Standard practice right? However, this leads me to some terribly messy code, because just about every control type I have, I need a MethodInvoker delegate or something. It's adding thousands of lines of code to my protects, and its terribly time consuming.
I currently have hundreds of "property setting" methods like:
private void Safe_SetLableText(Label control, string text)
{
if (control.InvokeRequired)
{
control.Invoke((MethodInvoker)delegate
{
control.Text = text;
});
}
else
{
control.Text = text;
}
}
So, is there some other technique, or way to do this, or some way to being able to always alter a property of a control, no matter what the control is and no matter what thread im in?
something like: (pseudocode)
BackgroundWorker.RunWorkerAsync();
private void thing_to_do()
{
// We are in a background thread now
DoSomeDatabaseWorkThatTakesALongTime();
InvokeAnyControls();
// Do some stuff...
controlX.Text = "123"
controlY.Height = 300;
controlZ.text = ControlA.text;
RestoreAnyControls();
}
You could wrap your InvokeRequired code with a delegate, like so:
public static void Invoke2<TControl>(this TControl c, Action<TControl> code) where TControl : Control {
if( c.InvokeRequired ) c.Invoke( delegate() { code(c); } );
else code(c);
}
Then use it like so:
private void Safe_SetLableText(Label control, string text) {
control.Invoke2( c => c.Text = text );
}
Of course you might want better names than Invoke2, but I hope the idea sits will with you. Note that the lambda-expression syntax is a C# 3.0 feature, but the Action<T> delegate is part of .NET 2.0, so this will compile against the .NET Framework 2.0 so long as you're VS2008 or later.
I'm posting an answer to my own question because I think it will add value to the community.
1) I wanted to "simplify" my code, and one if the most important finds was that that the:
control.InvokeRequired
really isnt needed... its pretty much a given. Importantly, you CAN rely on the fact that the control will need to be invoked if you are in a background (or non-UI) thread.
2) The invocation travels "UP" the control tree, so if you have:
Form > Control > Control inside Control > etc > etc
You only need to invoke "Form" (top most), and then you can alter the properties of the child elements.
So here is my clean and simple solution to working with background workers (or non-UI threads). I have just tested this now and it works great.
public partial class Form1: Form
{
public Form1()
{
BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(this.bgDoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.bgComplete);
bgw.RunWorkerAsync();
}
private void bgComplete(object sender, EventArgs e)
{
// You are not in the UI thread now, so you can Invoke without error
this.Invoke((MethodInvoker)delegate
{
// Now you can change any property an any control within this form.
// Remember "this" refers to Form1.
this.label1.Text = "test123";
this.label2.Text = "test456";
this.label3.Text = this.label4.Text;
// You can set progress bars too, not just label text
}
}
private void bgDoWork(object sender, DoWorkEventArgs e)
{
// Do something that takes a long time
}
}
As you are already using the Background worker why don't you 'misuse' OnProgressChanged?
private void thing_to_do()
{
// We are in a background thread now
DoSomeDatabaseWorkThatTakesALongTime();
BackgroundWorker.ReportProgress(1, "state");
DoSomeMoreDatabaseWorkThatTakesALongTime();
BackgroundWorker.ReportProgress(2, YourObjectHere);
}
void OnProgressChanged(ProgressChangedEventArgs progressArgs)
{
switch(progressArgs.ProgressPercentage)
{
case 1:
// Do some stuff...
controlX.Text = "123"
controlY.Height = 300;
controlZ.text = ControlA.text;
break;
case 2:
// other stuff
YourObject obj = (YourObject) progressArgs.UserState;
// wahtever...
break;
default:
break;
}
}

Why would MonoTouch not garbage collect my custom UIButton unless I call button.RemoveFromSuperview()?

There seems to be something holding a reference to my custom button, MyButton (which inherits from UIButton), causing it not to be garbage collected unless I remove it from the superview. This, in turn, would cause the view controller that it is on to also not be finalized and collected.
In my example, I have my custom button but I also have a standard UIButton on the view controller which does not need to be removed from the superview in order to be collected. What's the difference? Looks pretty similar to me.
See this code. The irrelevant lines were removed for example's sake. Some things to note about the sample:
-MyButton is pretty empty. Just a constructor and nothing else overridden.
-Imagine MyViewController being on a UINavigationController
-LoadView() just creates the buttons, hooks up an event for each and adds it to the view
-Touching _button would push another MyViewController to the nav controller
-I'm doing some reference cleanup when popping view controllers off the nav controller in ViewDidAppear()
-In CleanUpRefs() you'll see that I have to remove _myButton from superview in order for all the objects to be garbage collected. _button, on the other hand does not need to be removed.
-I'm expecting the entire MyViewController to be collected, including all subviews, when popping from the nav controller but commenting out _myButton.RemoveFromSuperview() stops this from happening.
public class MyViewController : UIViewController
{
private UIButton _button;
private MyButton _myButton;
private MyViewController _nextController;
public override void LoadView()
{
base.LoadView();
_button = UIButton.FromType(UIButtonType.RoundedRect);
_button.TouchUpInside += PushNewController;
View.AddSubview(_button);
_myButton = new MyButton();
_myButton.TouchUpInside += MyButtonTouched;
View.AddSubview(_myButton);
}
private void PushNewController(object sender, EventArgs e)
{
_nextController = new MyViewController();
NavigationController.PushViewController(_nextController, true);
}
private void MyButtonTouched(object sender, EventArgs e)
{
Console.WriteLine("MyButton touched");
}
public void CleanUpRefs()
{
//_button.RemoveFromSuperview();
_myButton.RemoveFromSuperview();
// remove reference from hooking up event handler
_button.TouchUpInside -= PushNewController;
_myButton.TouchUpInside -= MyButtonTouched;
_button = null;
_myButton = null;
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
if(_nextController != null)
{
_nextController.CleanUpRefs();
_nextController = null;
}
}
}
It seems as if there's something different with the fact that MyButton isn't a straight UIButton in that it is inherited. But then again, why would there be an extra reference count to it that's being removed by calling RemoveFromSuperview() especially when there's a UIButton just like it that doesn't need to be removed?
(I apologize for the really bad layout, stackoverflow seems to have problems laying out bullets right above code snippets)
Update: I filed a bug report with the MonoTouch team. You can download the sample project from there if you want to run it. Bug 92.
The reason for not garbage collecting in that scenario is just a bug in MonoTouch.
The upcoming MonoTouch release will contain a fix for this. If you are in a hurry, you can replace your /Developer/MonoTouch/usr/lib/mono/2.1/monotouch.dll with the copy I placed here:
http://tirania.org/tmp/monotouch.dll
I would make a backup, in case I did something wrong in my work-in-progress library.

Resources