Reputation: 79
I tried to implement MVVM using MessagingCenter from my ViewModel. I obtained the following error because multiples threads receive the same Message "ClearStackLayout" and don't wait the callback's end of each other :
Index was outside the bounds of the array.
Here is my View code :
public partial class LibraryChoicePage : DefaultBackgroundPage {
private Object thisLock = new Object();
public LibraryChoicePage() {
InitializeComponent();
/* ClearStackLayout */
MessagingCenter.Subscribe<LibraryChoiceViewModel>(this, "ClearStackLayout", (sender) => {
lock (thisLock) {
this._choices.Children.Clear();
}
});
/* AddToStackLayout */
MessagingCenter.Subscribe<LibraryChoiceViewModel, View>(this, "AddToStackLayout", (sender, arg) => {
lock (thisLock) {
this._choices.Children.Add(arg);
}
});
}
}
Upvotes: 1
Views: 6093
Reputation: 74094
The number one thing is always call StackLayout.Children.Clear|Add
on the UI thread. iOS does not like when removing UIView
subviews off the main UI thread and will throw exceptions and can even cause native crashes
This is how I would serialized
the messaging calls:
var semaphone = new SemaphoreSlim(1);
MessagingCenter.Subscribe<object>(this, "ClearStackLayout", async (sender) =>
{
await semaphone.WaitAsync();
Device.BeginInvokeOnMainThread(() =>
{
_choices.Children.Clear();
});
semaphone.Release();
});
MessagingCenter.Subscribe<object, View>(this, "AddToStackLayout", async (sender, arg) =>
{
await semaphone.WaitAsync();
Device.BeginInvokeOnMainThread(() =>
{
_choices.Children.Add(arg);
});
semaphone.Release();
});
Note: try/finally
should be wrapping the SemaphoreSlim.Release
and a catch
to execute any recoverer code needed from add/clear failures.
UIUnit Parallel Test Method:
Random random = new Random();
var tasks = new List<Task>();
for (int i = 0; i < 50; i++)
{
if (random.NextDouble() > .1)
tasks.Add(Task.Factory.StartNew(() => { AddLayout(); }));
else
tasks.Add(Task.Factory.StartNew(() => { ClearLayout(); }));
}
var completed = Task.Factory.ContinueWhenAll(tasks.ToArray(), (messagecenterTasks) => {
foreach (var task in messagecenterTasks)
{
if (task.Status == TaskStatus.Faulted)
{
D.WriteLine("Faulted:");
D.WriteLine($" {task.Exception.Message}");
}
}
}).Wait(1000);
if (!completed)
D.WriteLine("Some tasks did not complete in time allocated");
Note: AddLayout/ClearLayout are method wrappers for the MessageCenter.Send
of AddToStackLayout
and ClearStackLayout
.
Upvotes: 8