Android Question Problem with Wait For and asynchronous code in library?

RJB

Active Member
Licensed User
Longtime User
Using something like the code below the 'following code' in the 'CallingSub' is never executed and (of course) other code continues before the asynchronous call completes.

I really need to 'wait for' the asynchronous code to complete before continuing with the 'following code'

How do I do that?

Thanks


B4X:
Sub Class_Globals
    Dim WiFi As MLwifi
End Sub

Sub CallingSub
    Try
        'Preceding code
        'Call 'CalledSub' and wait for result
        wait for (CalledSub) complete (Result As Boolean)
        'returns with 'Result' not set?
        'so following code never executes
        Log("Following code")
        Return Result

    Catch
        Log(LastException)
'e.g.: (NullPointerException) java.lang.NullPointerException
'etc

    End Try
End Sub

Sub CalledSub As ResumableSub
    'Call e.g.asynchronous code in a Library and wait for result
    WiFi.reconnectWifiAP("SSID", 10000)
    wait for Wifi_ConnectionResult(Success As Boolean) 'Returns immediately without return value set?
    Return Success
End Sub
 

RJB

Active Member
Licensed User
Longtime User
Sorry but I don't understand:
If the ConnectionResult was raised then wouldn't the following line return true/ false, rather than the NullPointerException being reported in the calling sub?
I assumed that the Wait For is causing the calling sub to continue, whilst it waits?
Sorry if my description was not clear.
 
Upvote 0

RJB

Active Member
Licensed User
Longtime User
Maybe you don't need to use Wait For.
I've have already modified about 10 other subs to remove the Wait Fors and Sleeps but I have one left that gets complicated by the time you have a _ConnectionResult sub to handle the async result and then try to return a value to the original calling sub. If Waitfor and sleep can be used it's so much easier and more maintainable.
 
Upvote 0

RJB

Active Member
Licensed User
Longtime User
Now I see it in the comments.

Remove the try / catch block.
Run the program in release mode and post the logs.
This is the log from running the full/ original code in release mode:
B4X:
(NullPointerException) java.lang.NullPointerException
asyncfunctions$ResumableSub_RemoveSendresume (java line: 2648)
java.lang.NullPointerException
    at anywheresoftware.b4a.keywords.Common.WaitFor(Common.java:1781)
    at com.AdzDisplay.AdzMicroControl.asyncfunctions$ResumableSub_RemoveSend.resume(asyncfunctions.java:2648)
    at com.AdzDisplay.AdzMicroControl.asyncfunctions._vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv3(asyncfunctions.java:2567)
    at com.AdzDisplay.AdzMicroControl.tools$ResumableSub_RemoveSensor.resume(tools.java:2083)
    at anywheresoftware.b4a.BA.checkAndRunWaitForEvent(BA.java:275)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:215)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:201)
    at anywheresoftware.b4a.keywords.Common$1.onClick(Common.java:490)
    at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:166)
    at android.os.Handler.dispatchMessage(Handler.java:110)
    at android.os.Looper.loop(Looper.java:193)
    at android.app.ActivityThread.main(ActivityThread.java:5292)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:824)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:640)
    at dalvik.system.NativeStart.main(Native Method)
 
Upvote 0

RJB

Active Member
Licensed User
Longtime User
As requested:
B4X:
Remove: A1
13:30:58: ASNR Connect to: 192.168.1.87
13:30:58: ASNR 1
13:30:58 CTS 1
13:30:58: CTS 2
asyncfunctions$ResumableSub_AsyncSend_NoReturnresume (java line: 390)
java.lang.NullPointerException
    at anywheresoftware.b4a.keywords.Common.WaitFor(Common.java:1781)
    at com.AdzDisplay.AdzMicroControl.asyncfunctions$ResumableSub_AsyncSend_NoReturn.resume(asyncfunctions.java:390)
    at com.AdzDisplay.AdzMicroControl.asyncfunctions._asyncsend_noreturn(asyncfunctions.java:350)
    at com.AdzDisplay.AdzMicroControl.asyncfunctions$ResumableSub_RemoveSend.resume(asyncfunctions.java:2545)
    at com.AdzDisplay.AdzMicroControl.asyncfunctions._removesend(asyncfunctions.java:2464)
    at com.AdzDisplay.AdzMicroControl.tools$ResumableSub_RemoveSensor.resume(tools.java:1862)
    at anywheresoftware.b4a.BA.checkAndRunWaitForEvent(BA.java:275)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:215)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:201)
    at anywheresoftware.b4a.keywords.Common$1.onClick(Common.java:490)
    at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:166)
    at android.os.Handler.dispatchMessage(Handler.java:110)
    at android.os.Looper.loop(Looper.java:193)
    at android.app.ActivityThread.main(ActivityThread.java:5292)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:824)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:640)
    at dalvik.system.NativeStart.main(Native Method)

Below is the actual code with unrelated code removed:

B4X:
Public Sub AsyncSend_NoReturn(IP As String, Count As Long, SendType As Int, Info As Long, ExtraInfo As String, SendData() As Byte) As ResumableSub
    Log(DateTime.Time(DateTime.Now) & ": ASNR Connect to: " & IP)

        Log(DateTime.Time(DateTime.Now) & ": ASNR 1")
    
        wait for (ConnectToServer(IP, 5)) complete (ASNRSuccess As Boolean)
        
        Log(DateTime.Time(DateTime.Now) & ": ASNR 2")
        If ASNRSuccess Then
            Log(DateTime.Time(DateTime.Now) & ": ASNR 3")
            Dim DataBytes() As Byte
            DataBytes = EncryptDataBytes(Ser.ConvertArrayToBytes(Array(Count, SendType, Info, ExtraInfo, SendData)))
            Dim Result As Boolean = SendAstream.Write(DataBytes)
            Log(DateTime.Time(DateTime.Now) & ": ASNR 4")
            Return Result
        Else
            Log(DateTime.Time(DateTime.Now) & ": ASNR 5")
            Return False
        End If

End Sub

Public Sub ConnectToServer(IP As String, NumberOfTries As Int) As ResumableSub
    Log(DateTime.Time(DateTime.Now) & " CTS 1")

    Dim SendClient As Socket
        
    For i = 0 To NumberOfTries - 1
        SendClient.Initialize("SendClient")
        Dim Y As Int = AsyncSendPortWaitTimes.Length - 1
        If i < AsyncSendPortWaitTimes.Length - 1 Then Y = i
        SendClient.Connect(IP, AsyncSendPort, AsyncSendPortWaitTimes(Y))
        Log(DateTime.Time(DateTime.Now) & ": CTS 2")
        Wait For SendClient_Connected (Successful As Boolean)
        Log(DateTime.Time(DateTime.Now) & ": CTS 3")
        If Successful Then
            Log(DateTime.Time(DateTime.Now) & " CTS 4")
            SendAstream.InitializePrefix(SendClient.InputStream, False, SendClient.OutputStream, "SendAstream")
            Log(DateTime.Time(DateTime.Now) & " CTS 5")
            Return True
        End If
    Next
    LogColor(DateTime.Time(DateTime.Now) & " Failed to connect: " & IP, Colors.red)
    Return False
End Sub
 
Upvote 0

RJB

Active Member
Licensed User
Longtime User
No, no threads.. Unless a library does, MLwifi400?

There's quite a lot in the project so I'll try to put something together using only the relevant modules. It'll take a little while though!
I've spent quite a while over the last week trying to work out what's going on and the nearest I got was that the Wait For (or Sleep) caused a return to the calling sub whilst it waited, but that it hadn't initialised the return (resumablesub) value. I couldn't locate a fault in the calling or called sub, it seemed to be somewhere in between if that makes any sense.
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
I found this in the MLWifi Sources (i found a copy somewhere in the forum).
Maybe related. Never used the lib though.

B4X:
public void connectWifiAP(BA ba, String ssid, int security, String password, int timeout) {
        if (debug)
            BA.Log("SDK: " + Build.VERSION.SDK_INT);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            tmr = new Timer();
            tmr.schedule(new TimeoutTask(ba), timeout);
            connectWifiAP2(ba, ssid, "", security, password);
        } else {
            connectWifiAPQ(ba, ssid, security, password, timeout);
        }
    }

static private void connectToWifi(BA ba, WifiManager wm, int NetId, String ssid) {
    try {
        connectionSuccess = false;
        if (debug)
            BA.Log("[MLwifi] Connecting to " + ssid);


        if (receiverRegistered) {
            unregisterReceiver(broadcastReceiver);
        }

        if (broadcastReceiver == null) {
            final IntentFilter intentFilter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION);
            broadcastReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    if (!intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
                        if (debug)
                            BA.Log("BroadcastReceiver Intent Action: " + intent.getAction());
                    }
                    // Post connection
                    @SuppressLint("MissingPermission") NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
                    if (networkInfo != null) {
                        if (debug)
                            BA.Log(networkInfo.toString());
                        if (networkInfo.isConnected()) { //&& networkInfo.getExtraInfo().equals(ssid)) {
                            WifiManager wifiManager = (WifiManager) BA.applicationContext.getSystemService(Context.WIFI_SERVICE);
                            WifiInfo info = wifiManager.getConnectionInfo();
                           // BA.Log(info.getSSID() + " : " + ssid);
                            if (info.getSSID().equals(ssid)) {
                                if (ba.subExists("wifi_connectionresult") && networkInfo.getState() == NetworkInfo.State.CONNECTED && !connectionSuccess) {
                                    connectionSuccess = true;
                                    if (debug)
                                        BA.Log("[MLwifi] Connected to " + info.getSSID());
                                    tmr.cancel();
                                    ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);
                                    exec.schedule(new Runnable() {
                                        public void run() {
                                            ba.raiseEvent(this, "wifi_connectionresult", true);
                                        }
                                    }, 1, TimeUnit.SECONDS);

                                }
//                                } else {
//                                    if (ba.subExists("wifi_connectionresult")) {
//                                        ba.raiseEvent(this, "wifi_connectionresult", false);
//                                    }
                            }
                        } else if (!networkInfo.isAvailable() && networkInfo.getExtraInfo().equals(ssid)) {
                            tmr.cancel();
                            if (debug)
                                BA.Log("[MLwifi] " + ssid + " not available");
                            if (ba.subExists("wifi_connectionresult")) {
                                ba.raiseEvent(this, "wifi_connectionresult", false);
                            }
                        }
                    }
                }
            };
            BA.applicationContext.registerReceiver(broadcastReceiver, intentFilter);
            receiverRegistered = true;
        }
        wm.enableNetwork(NetId, true);

    } catch (Exception e) {
        BA.LogError(e.getLocalizedMessage());
    }
}

static class TimeoutTask extends TimerTask {
    BA _ba;
    public TimeoutTask(BA ba) {
        _ba = ba;
    }
    public void run() {
        tmr.cancel(); //Terminate the timer thread
        if (_ba.subExists("wifi_connectionresult")) {
            _ba.raiseEvent(this, "wifi_connectionresult", false);
        }
        if (receiverRegistered) {
            unregisterReceiver(broadcastReceiver);
        }
    }
}
 
Reactions: RJB
Upvote 0

RJB

Active Member
Licensed User
Longtime User
Thanks DonManfred. I found the source at: https://www.b4x.com/android/forum/threads/mlwifi-library-updated-to-v4-00.125051/. I'll take a look and see if it helps.

I've done some more testing with the following results - I don't know if it helps at all!

The code is something like the following:
B4X:
Sub CallingSub1()    'Note: no return type
     CallingSub2(AList)
End Sub


Public Sub CallingSub2(BList As List) As ResumableSub

.......
Try
    wait for (CallingSub3(.........)) complete (Result As Boolean)
Catch
    Log("CS2: " & LastException)
End Try

..........

Return True    'Note: Returns a fixed value

End Sub


Public Sub CallingSub3(............) As ResumableSub

........
    Try
        wait for (.............) complete (ASNRSuccess As Boolean)
.......
    Catch
        LogColor("CS3: " & LastException, Colors.red)
    End Try
    Return False    'Note: Returns a fixed value
End Sub

Public Sub ConnectToServer(............) As ResumableSub

...........

        SendClient.Initialize("SendClient")
        SendClient.Connect(IP, AsyncSendPort, AsyncSendPortWaitTime)

        Wait For SendClient_Connected (Successful As Boolean)

        Log(DateTime.Time(DateTime.Now) & ": CTS 3")
        If Successful Then
            SendAstream.InitializePrefix(SendClient.InputStream, False, SendClient.OutputStream, "SendAstream")
            Return True
        End If

    Return False    'Note: Returns a fixed value

End Sub

where CallingSub1 calls CallingSub2, which calls CallingSub3, which calls ConnectToServer.
In this case both CallingSub3 and CallingSub2 log (NullPointerException) java.lang.NullPointerException

If I change the code to:
B4X:
Sub CallingSub1()    'Note: no return type
     CallingSub2(AList)
End Sub


Public Sub CallingSub2(BList As List) As ResumableSub

.......
Try
    wait for (CallingSub3(.........)) complete ' Don't expect a value (Result As Boolean)
Catch
    Log("CS2: " & LastException)
End Try

..........

Return True    'Note: Return a fixed value

End Sub


Public Sub CallingSub3(............) ' Don't return a value As ResumableSub

........
    Try
        wait for (.............) complete (ASNRSuccess As Boolean)
.......
    Catch
        LogColor("CS3: " & LastException, Colors.red)
    End Try
    ' Don't return a value Return False    'Note: Return a fixed value
End Sub

Public Sub ConnectToServer(............) As ResumableSub

...........

        SendClient.Initialize("SendClient")
        SendClient.Connect(IP, AsyncSendPort, AsyncSendPortWaitTime)

        Wait For SendClient_Connected (Successful As Boolean)

        Log(DateTime.Time(DateTime.Now) & ": CTS 3")
        If Successful Then
            SendAstream.InitializePrefix(SendClient.InputStream, False, SendClient.OutputStream, "SendAstream")
            Return True
        End If

    Return False    'Note: Return a fixed value

End Sub

so that CallingSub3 doesn't return a value to CallingSub2 then only CallingSub3 logs the (NullPointerException) java.lang.NullPointerException.

As I say, don't know if that helps?
 
Upvote 0

RJB

Active Member
Licensed User
Longtime User
I THINK I found what caused this i.e. I've changed the code and it now works!

What it seemed to be is as follows:

I called a sub (using Wait For(CallSub2(TMod, TSub, True)) Complete) which sent some information to an ESP32 and then returned.
However the response was received and the _NewData sub took action before the above return completed.
The return never completed (or at least not till a long time later) so the code that should have run didn't.

Wait for is very useful until you do something wrong. Then it can be very difficult to find the problem!!
 
Upvote 0
Cookies are required to use this site. You must accept them to continue using the site. Learn more…