SwiftUI. Access to properties from background - multithreading

This problem is driving me crazy.
In principle the code below works correctly.
The authenticator that is called is a class defined in its own module (Authenticator.swift).
The code below is a method of the viewModel that conforms ObservableObject protocol.
The observed properties self.error and self.goToHomeView are properties published from the viewModel.
The authenticator method authenticates with FaceID. If authentication succeeds, error is nil, then the observed variable goToHomeView is set to true to switch to the Home view.
If error is not nil, then self.error is assigned which is of type LocalizedError and that triggers an alert in the view.
The problem is that an annoying warning appears all the time saying: "Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.”
I would like to know if anyone knows a technically valid way to modify values from the main thread from a secondary thread running in background.
Task {
await authenticator.authenticate { error in
if let error = error {
self.error = error
} else {
self.goToHomeView = true
}
}
}

Related

How do I get a parameter to not just display in a component, but also be recognized inside of OnInitializedAsync()?

I'm working on a blazor server-side project and I have a component that gets passed a model (pickedWeek) as a parameter. I can use the model fine in-line with the html, but OnInitializedAsync always thinks that the model is null.
I have passed native types in as parameters, from the Page into a component, this way without an issue. I use a NullWeek as a default parameter, so the number getting used in OnInitializedAsync only ever appears to be from the NullWeek. In case this is related, there is a sibling component that is returning the Week model to the Page through an .InvokeAsync call, where StateHasChanged() is being called after the update. It appears that the new Week is getting updated on the problem component, but that OnInitializeAsync() either doesn't see it, or just never fires again- which maybe is my problem, but I didn't think it worked that way.
For instance, the below code will always show "FAILURE" but it will show the correct Week.Number. Code below:
<div>#pickedWeek.Number</div>
#if(dataFromService != null)
{
<div>SUCCESS</div>
}
else
{
<div>FAILURE</div>
}
#code{
[Parameter]
public Week pickedWeek { get; set; }
protected IEnumerable<AnotherModel> dataFromService { get; set; }
protected override async Task OnInitializedAsync()
{
if (pickedWeek.Number > 0)
{
dataFromService = await _injectedService.MakeACall(pickedWeek.Id);
}
}
}
#robsta has this correct in the comments, you can use OnParametersSet for this. Then, you will run into another issue, in that each rerender will set your parameters again and generate another call to your service. I've gotten around this by using a flag field along with the the OnParametersSet method. Give this a shot and report back.
private bool firstRender = true;
protected override async Task OnParametersSetAsync()
{
if (pickedWeek.Number > 0 && firstRender)
{
dataFromService = await _injectedService.MakeACall(pickedWeek.Id);
firstRender = false;
// MAYBE call this if it doesn't work without
StateHasChanged();
}
}
Another alternative is to use the OnAfterRender override, which supplies a firstRender bool in the the method signature, and you can do similar logic. I tend to prefer the first way though, as this second way allows it to render, THEN sets the value of your list, THEN causes another rerender, which seems like more chatter than is needed to me. However if your task is long running, use this second version and build up a loading message to display while the list is null, and another to display if the service call fails. "FAILURE" is a bit misleading as you have it as it's being displayed before the call completes.
I've also found that a call to await Task.Delay(1); placed before your service call can be useful in that it breaks the UI thread loose from the service call awaiter and allows your app to render in a loading state until the data comes back.

Child thread not seeing updates made by main thread

I'm implementing a SparkHealthListener by extending SparkListener class.
#Component
class ClusterHealthListener extends SparkListener with Logging {
val appRunning = new AtomicBoolean(false)
val executorCount = new AtomicInteger(0)
override def onApplicationStart(applicationStart: SparkListenerApplicationStart) = {
logger.info("Application Start called .. ")
this.appRunning.set(true)
logger.info(s"[appRunning = ${appRunning.get}]")
}
override def onExecutorAdded(executorAdded: SparkListenerExecutorAdded) = {
logger.info("Executor add called .. ")
this.executorCount.incrementAndGet()
logger.info(s"[executorCount = ${executorCount.get}]")
}
}
appRunning and executorCount are two variables declared in ClusterHealthListener class. ClusterHealthReporterThread will only read the values.
#Component
class ClusterHealthReporterThread #Autowired() (healthListener: ClusterHealthListener) extends Logging {
new Thread {
override def run(): Unit = {
while (true) {
Thread.sleep(10 * 1000)
logger.info("Checking range health")
logger.info(s"[appRunning = ${healthListener.appRunning.get}] [executorCount=${healthListener.executorCount.get}]"
}
}
}.start()
}
ClusterHealthReporterThread is always reporting the initialized values regardless of the changes made to the variable by main thread? What am I doing wrong? Is this because I inject healthListener to ClusterHealthReporterThread?
Update
I played around a bit and looks like it has something to do with the way i initiate spark listener.
If I add the spark listener like this
val sparkContext = SparkContext.getOrCreate(sparkConf)
sparkContext.addSparkListener(healthListener)
Parent thread will show appRunning as 'false' always but shows executor count correctly. Child thread (health reporter) will also show proper executor counts but appRunning was always reporting 'false' like that of the main thread.
Then I stumbled across this Why is SparkListenerApplicationStart never fired? and tried setting listener at the spark config level,
.set("spark.extraListeners", "HealthListener class path")
If I do this, main thread will report 'true' for appRunning and will report correct executor counts but child thread will always report 'false' and '0' value for executors.
I can't immediately see what's wrong here, you might have found an interesting edge case.
I think #m4gic's comment might be correct, that the logging library is perhaps caching that interpolated string? It looks like you are using https://github.com/lightbend/scala-logging which claims that this interpolation "has no effect on behavior", so maybe not. Please could you follow his suggestion to retry without using that feature and report back?
A second possibility: I wonder if there is only one ClusterHealthListener in the system? Perhaps the autowiring is causing a second instance to be created? Can you log the object ids of the ClusterHealthListener reference in both locations and verify that they are the same object?
If neither of those suggestions fix this, are you able to post a working example that I can play with?

passing around NSManagedObjects

I get strange errors when I am trying to pass around NSManagedObject through several functions. (all are in the same VC).
Here are the two functions in question:
func syncLocal(item:NSManagedObject,completionHandler:(NSManagedObject!,SyncResponse)->Void) {
let savedValues = item.dictionaryWithValuesForKeys([
"score",
"progress",
"player"])
doUpload(savedParams) { //do a POST request using params with Alamofire
(success) in
if success {
completionHandler(item,.Success)
} else {
completionHandler(item,.Failure)
}
}
}
func getSavedScores() {
do {
debugPrint("TRYING TO FETCH LOCAL SCORES")
try frc.performFetch()
if let results = frc.sections?[0].objects as? [NSManagedObject] {
if results.count > 0 {
print("TOTAL SCORE COUNT: \(results.count)")
let incomplete = results.filter({$0.valueForKey("success") as! Bool == false })
print("INCOMPLETE COUNT: \(incomplete.count)")
let complete = results.filter({$0.valueForKey("success") as! Bool == true })
print("COMPLETE COUNT: \(complete.count)")
if incomplete.count > 0 {
for pendingItem in incomplete {
self.syncScoring(pendingItem) {
(returnItem,response) in
let footest = returnItem.valueForKey("player") //only works if stripping syncScoring blank
switch response { //response is an enum
case .Success:
print("SUCCESS")
case .Duplicate:
print("DUPLICATE")
case .Failure:
print("FAIL")
}
}
} //sorry for this pyramid of doom
}
}
}
} catch {
print("ERROR FETCHING RESULTS")
}
}
What I am trying to achieve:
1. Look for locally saved scores that could not submitted to the server.
2. If there are unsubmitted scores, start the POST call to the server.
3. If POST gets 200:ok mark item.key "success" with value "true"
For some odd reason I can not access returnItem at all in the code editor - only if I completely delete any code in syncLocal so it looks like
func syncLocal(item:NSManagedObject,completionHandler:(NSManagedObject!,SyncResponse)->Void) {
completionHandler(item,.Success)
}
If I do that I can access .syntax properties in the returning block down in the for loop.
Weirdly if I paste the stuff back in, in syncLocal the completion block keeps being functional, the app compiles and it will be executed properly.
Is this some kind of strange XCode7 Bug? Intended NSManagedObject behaviour?
line 1 was written with stripped, line 2 pasted rest call back in
There is thread confinement in Core Data managed object contexts. That means that you can use a particular managed object and its context only in one and the same thread.
In your code, you seem to be using controller-wide variables, such as item. I am assuming the item is a NSManagedObject or subclass thereof, and that its context is just one single context you are using in your app. The FRC context must be the main thread context (a NSManagedObjectContext with concurrency type NSMainThreadConcurrencyType).
Obviously, the callback from the server request will be on a background thread. So you cannot use your managed objects.
You have two solutions. Either you create a child context, do the updates you need to do, save, and then save the main context. This is a bit more involved and you can look for numerous examples and tutorials out there to get started. This is the standard and most robust solution.
Alternatively, inside your background callback, you simply make sure the context updates occur on the main thread.
dispatch_async(dispatch_get_main_queue()) {
// update your managed objects & save
}

TypeInitializationException/ArgumentException when referencing initialized variable

I just received an exception when I try to reference a static variable in another class, which is also statically initialized. This worked before, and for some reason it fails now. The only changes I made were resetting Visual Studio (2010) to its default setting, which I can't imagine to be the reason for this. Any other code I added didn't touch any of the affected parts either.
This is my code
WinForms class 'MainForm':
partial class MainForm : Form
{
// ...
private RefClass convMan;
private Dictionary<EnumType, string> LogNames = RefClass.LogNames;
// ...
public MainForm() { .... }
}
Referenced class 'RefClass':
class RefClass
{
// ...
public enum EnumType { TypeOne = 0, TypeTwo = 1, TypeThree = 2 };
public static Dictionary<EnumType, string> LogNames = new Dictionary<EnumType, string>()
{
{ EnumType.TypeOne, "Text0" },
{ EnumType.TypeTwo, "Text1" },
{ EnumTypy.TypeThree, "Text2" }
};
}
The error I get now is (translated from German):
An unhandled exception of type "System.TypeInitializationException" occurred.
Additional information: The type initializer for "RefClass" threw an exception.
which has the InnerException
System.ArgumentException
So, as far as I'm concerned, my static dictionary should be initialized once it gets accessed, thus when my Form class references it. I tried debugging to see if the static dictionary is initialized before it gets referenced in the Form class, which is not the case. Also, when I stop at a breakpoint for the reference line, the variable LogNames is null.
I'm really confused as to why this happens, it all worked before.
I found my error, the exceptions I got were quite misleading though. It was a problem with a different dictionary than the one I referenced. It probably didn't get initialized in the first place because something before that failed (If someone can clear this up, please feel free to do so!). Basically what I did wrong was using a two-directional dictionary and adding a value twice. This should normally produce a normal exception, but since it was done statically it got wrapped into a TypeInitializationException. I had a deeper look into the exact stacktrace of the inner exception and found where the exception originated from. Maybe this helps someone in the future...
I had a simular issue getting the same exception. Found that my static constructor for my utility class was generating the exception. Took some time locating since the description of the exception was misleading.
As #Yeehaw mentioned, it appears that the exception gets wrapped, so the common denominator here I would say is that the class/object is static.

Error in property accessor

We recently placed an MVC application in production that sometimes gives us unhandled errors that we cannot reproduce in our development environment. The web app keeps crashing on the same line but the inner exception gives different error messages. The line where it crashes is when a particular property is accessed (property accessor). But the inner exception gives us the following different errors:
Unable to cast object of type 'System.Int32' to type 'System.String'.
Index was outside the bounds of the array.
There is already an open DataReader associated with this Command which must be closed first.
We have a large number of users so I'm assuming that this has to do with concurrency or some type of thread problem? Can someone guide me in the right direction on how to find the problem?
By the way, it crashes always when accessing the same property. Sometimes it works other times it fails.
Here is the code for the accessor:
public string DepartementSelectionne
{
get
{
if (string.IsNullOrEmpty(m_departementSelectionne) == true)
if (ListeDepartements.FirstOrDefault() != null)
m_departementSelectionne = ListeDepartements.First().CodeDepartement;
return m_departementSelectionne;
}
set
{
m_departementSelectionne = value;
}
}
Thanks!

Resources