>
> I'm interested in getting feedback on this solution or suggestions on the preferred way to implement a semaphore in Tasker.
>
Here is what I would do.
Create 1 task that will do the http get and give it a user name. Then when you want to do a http get from any other task use the preform task action and send any necessary info in %par1 and %par2. And use the return action to get the info back. Just before the preform task action do a wait until action and wait until %TRUN !~ *,Name Of The HTTP get Task,*
The *,,* are important, see pattern matching in the guide.
%TRUN contains a comma separated list of all "User Named" running tasks
My workaround: I do not use HttpGet but do all my http requests with Javascript.
- only one js action at a certain time performing
- error handling is easier
- data handling is easier (json, XML, regex, string functions)
Juergen.
> As promised, here is my updated, simpler recipe for implementing a lock semaphore to make HTTP Get actions safe in a multiple concurrent task environment... There are two tasks created named getHttpLock and setHttpLock.
I honestly am having difficulty following your approach, and trying to figure it with multiple iterations of the lock task running makes figuring all potential outcomes very difficult.
Personally I would not trust any task set to 'run both together' that sets a global variable for flow control.
I agree with your issues with my first approach. I would suggest instead of doing the http get in the child task just use the child task as the lock.
However, There is also a potential for it to fail. No matter what kind of test you do before a the preform task action there is a potential of failure. Consider the following with task 1 and task 2 having the same priority and alternating actions.
(Task 1) -Wait until %trun !~ *,Task Name,* // this action proves true and the next action will be preformed//
(Task 2) -Wait until %trun !~ *,Task Name,* // this action proves true and the next action will be preformed//
(Task 1) - preform task // task starts , if you use %priority+1 this will ensure the child runs to completion before task 1 or task 2 continue however once the child completes I believe the next action will be from task 2
(Task 2) - preform task
This could cause potential issues.
I believe the answer would be to incorporate the 'test' into the preform task action then have a response from the child (from the return action) to the parent to prove the preform task action was completed. something like this..
Http Get (781)
<start loop>
A1: Wait Until [ MS:0 Seconds:2 Minutes:0 Hours:0 Days:0 ] If [ %TRUN !~ *,Lock,* ]
A2: Perform Task [ Name:Lock Priority:%priority+1 Parameter 1 (%par1): Parameter 2 (%par2): Return Value Variable:%lock Stop:Off ] If [ %TRUN !~ *,Lock,* ]
A3: Goto [ Type:Action Label Number:1 Label:start loop ] If [ %lock !~ true ]
A4: HTTP Get [ Server:Port:%test Path: Attributes: Cookies: User Agent: Timeout:10 Mime Type: Output File: Trust Any Certificate:Off ]
A5: Variable Set [ Name:%set_your_variables To:%HTTPD Do Maths:Off Append:Off ]
A6: Stop [ With Error:Off Task:Lock ]
Lock (782)
A1: Return [ Value:true Stop:Off ]
A2: Wait [ MS:0 Seconds:0 Minutes:10 Hours:0 Days:0 ]
So now the preform task action will only run if the child task is not running (lock is off).
If this fails (lock is on) %lock will not get set and it will go back to the top of the loop.
If the preform action runs then %lock returns and matches 'true' and the http get will run. The child task continues running because of the wait action (so the lock remains on until the 'Stop task' action.
> Although I liked your idea, it doesn't seem to be working for me. The calling task always blocks on the Perform Task action until the wait inside the Lock task is done:
Oh crap.. I forgot there is a mechanism in place that ensures a child task with a higher priority then the parent will finish before the parent is allowed to continue. Without the parent/child relation whenever a wait is encountered, lower priority tasks are allowed to run (this is what I thought would happen).
So unfortunately we will have to introduce another task for this approach to. Work. The child task will have to start the lock task then when the child task stops the parent can continue.
Here is what I have so far..
Http Get (781)
<start loop>
A1: Wait Until [ MS:0 Seconds:2 Minutes:0 Hours:0 Days:0 ] If [ %TRUN !~ *,Lock*,* ]
A2: Perform Task [ Name:Lock Priority:%priority Parameter 1 (%par1): Parameter 2 (%par2): Return Value Variable:%lock Stop:Off ] If [ %TRUN !~ *,Lock*,* ]
A3: Goto [ Type:Action Label Number:1 Label:start loop ] If [ %lock !~ true ]
A4: [X] HTTP Get [ Server:Port:%test Path: Attributes: Cookies: User Agent: Timeout:10 Mime Type: Output File: Trust Any Certificate:Off ]
A5: Flash [ Text:HTTP GET Long:On ]
A6: Wait [ MS:0 Seconds:3 Minutes:0 Hours:0 Days:0 ]
A7: Variable Set [ Name:%set_your_variables To:%HTTPD Do Maths:Off Append:Off ]
A8: Stop [ With Error:Off Task:Lock 2 ]
A9: Flash [ Text:DONE Long:On ]
Lock (782)
A1: Return [ Value:true Stop:Off ]
A2: Flash [ Text:After return Long:On ]
A3: [X] Wait [ MS:0 Seconds:10 Minutes:0 Hours:0 Days:0 ]
A4: [X] Flash [ Text:After wait Long:On ]
A5: Perform Task [ Name:Lock 2 Priority:%priority-1 Parameter 1 (%par1): Parameter 2 (%par2): Return Value Variable: Stop:On ]
Lock 2 (784)
A1: Flash [ Text:Lock 2 Long:On ]
A2: Wait [ MS:0 Seconds:10 Minutes:0 Hours:0 Days:0 ]
A3: Flash [ Text:Lock 2 done Long:On ]
Sorry if my original post was not clear. I should have stated it was just a untested idea. My second approach has not been thoroughly tested either. It is just my first thought on how to over come the parent/child relation problem. There could be a more elegant way to do this however my tasker time is very limited at the moment.
Ok, I snuck in a little tasker time ... :)
Here is a better solution. You just need to have the lock task start another iteration of itself and set the collision to abort existing .
Again not entirely tested but seems to be working..
Http Get (781)
<start loop>
A1: Wait Until [ MS:0 Seconds:2 Minutes:0 Hours:0 Days:0 ] If [ %TRUN !~ *,Lock,* ]
A2: Perform Task [ Name:Lock Priority:%priority Parameter 1 (%par1): Parameter 2 (%par2): Return Value Variable:%lock Stop:Off ] If [ %TRUN !~ *,Lock,* ]
A3: Goto [ Type:Action Label Number:1 Label:start loop ] If [ %lock !~ true ]
A4: [X] HTTP Get [ Server:Port:%test Path: Attributes: Cookies: User Agent: Timeout:10 Mime Type: Output File: Trust Any Certificate:Off ]
A5: Flash [ Text:HTTP GET Long:On ]
A6: Wait [ MS:0 Seconds:3 Minutes:0 Hours:0 Days:0 ]
A7: Variable Set [ Name:%set_your_variables To:%HTTPD Do Maths:Off Append:Off ]
A8: Stop [ With Error:Off Task:Lock ]
A9: Flash [ Text:DONE Long:On ]
Lock (782)
Abort Existing Task
A1: Return [ Value:true Stop:Off ]
A2: Flash [ Text:After return Long:On ]
A3: Wait [ MS:0 Seconds:10 Minutes:0 Hours:0 Days:0 ] If [ %par1 ~ set ]
A4: Flash [ Text:After lock wait Long:On ]
A5: [X] Flash [ Text:After wait Long:On ]
A6: Perform Task [ Name:Lock Priority:%priority-1 Parameter 1 (%par1):set Parameter 2 (%par2): Return Value Variable: Stop:On ] If [ %par1 !~ set ]
I have come up with the following solution
Nice work, that looks like a great solution and very creative. Probably to best to stick with that.
I believe I was able to get the %TRUN approach working as well. There seems to be a bug when trying to invoke another iteration of the same task from within that task. (The same problem you had and ended up going to a 'run both together' solution.
The solution is to put the call as the last action in task (weird huh..) That one took a while to figure out... :(
If I get some time I will test and post just for a alternative. I will add a identifier to solve the global lock release issue. On that note I noticed you. Are using %TIMESMS and %caller. I had been thinking that just using %TIMESMS as the time stamp and identifier would suffice. Theory being 2 actions could not be completed within 1 ms. What are your thoughts on that?
BTW.. are you using the run log for debugging? This is a invaluable tool. Menu / more / run log.
I noticed you. Are using %TIMEMS and %caller1. I had been thinking that just using %TIMEMS as the time stamp and identifier would suffice. Theory being 2 actions could not be completed within 1 ms. What are your thoughts on that?
BTW.. are you using the run log for debugging? This is a invaluable tool. Menu / more / run log.
> That would certainly make things easier!!! But I wasn't sure how fast things could theoretically run and if there could be a collision within a millisecond. So I built it to be super-defensive at the cost of a bit more complexity.
Gotcha..
>> BTW.. are you using the run log for debugging? This is a invaluable tool. Menu / more / run log.
>
>
> No, but it would probably be worth my investigating. Debugging with these flash commands is not the easiest! Thanks.
>
Their is some good info in the docs about the run log but it has a few quirks as well.
1. All actions are logged when they are 'compleated' so for example a wait action will not be entered 'shown' until after the wait is over.
2. For some reason ( most likely same as above) when there is a 'perform task' action, the invoked task will be shown as running before the preform task action is shown.
OK, here is the working tested %TRUN approach. I included a ID for the locking task. The lock task sets a global variable to %caller(:) and returns that data to the calling task. Then this global variable is tested before the unlock task is called.
I was not sure why you had the extra 'getHttpLockTask' instead of just calling 'httpLock' directly from 'Query'. My guess is for test in purposes. In any event I left
'getHttpLockTask' and tested. All seems to work as expected.
Query (789)
Run Both Together
A1: Variable Set [ Name:%CfgHttpTimeoutSecs To:30 Do Maths:Off Append:Off ]
A2: Perform Task [ Name:getHttpLockTask Priority:%priority Parameter 1 (%par1): Parameter 2 (%par2): Return Value Variable:%http_lock Stop:Off ]
A3: Flash [ Text:HTTP GET Long:On ]
A4: Wait [ MS:0 Seconds:15 Minutes:0 Hours:0 Days:0 ]
A5: Flash [ Text:Unlocking
%http_lock Long:On ] If [ %http_lock ~ %Current_Lock ]
A6: Flash [ Text:Unlock failed
%http_lock
%Current_lock Long:On ] If [ %http_lock !~ %Current_Lock ]
A7: Perform Task [ Name:releaseHttpLockTask Priority:%priority Parameter 1 (%par1):%http_lock Parameter 2 (%par2): Return Value Variable: Stop:Off ] If [ %http_lock ~ %Current_Lock ]
Query Timeout (790)
A1: Variable Set [ Name:%CfgHttpTimeoutSecs To:30 Do Maths:Off Append:Off ]
A2: Perform Task [ Name:getHttpLockTask Priority:%priority Parameter 1 (%par1): Parameter 2 (%par2): Return Value Variable:%http_lock Stop:Off ]
A3: Flash [ Text:HTTP GET TIMEOUT Long:On ]
A4: Wait [ MS:0 Seconds:15 Minutes:1 Hours:0 Days:0 ]
A5: Flash [ Text:Unlocking
%http_lock Long:On ] If [ %http_lock ~ %Current_Lock ]
A6: Flash [ Text:Unlock failed
%http_lock
%Current_lock Long:On ] If [ %http_lock !~ %Current_Lock ]
A7: Perform Task [ Name:releaseHttpLockTask Priority:%priority Parameter 1 (%par1):%http_lock Parameter 2 (%par2): Return Value Variable: Stop:Off ] If [ %http_lock ~ %Current_Lock ]
A8: [X] Perform Task [ Name:releaseHttpLockTask Priority:%priority Parameter 1 (%par1):%http_lock Parameter 2 (%par2): Return Value Variable: Stop:Off ]
httpLock (791)
Run Both Together
<TODO DELETE>
A1: Flash [ Text:%TIMES
Running httpLock: %par1 Long:On ]
A2: If [ %par1 eq child ]
A3: Flash [ Text:%caller(:) Long:On ]
A4: Wait [ MS:0 Seconds:%CfgHttpTimeoutSecs + 15 Minutes:0 Hours:0 Days:0 ]
A5: [X] Wait [ MS:0 Seconds:2 Minutes:0 Hours:0 Days:0 ]
A6: Variable Clear [ Name:%Current_Lock Pattern Matching:Off ]
A7: Flash [ Text:Lock time expired Long:On ]
A8: Else
A9: Return [ Value:%caller(:) Stop:Off ]
A10: Variable Set [ Name:%Current_Lock To:%caller(:) Do Maths:Off Append:Off ]
A11: Perform Task [ Name:httpLock Priority:%priority - 1 Parameter 1 (%par1):child Parameter 2 (%par2): Return Value Variable: Stop:On ]
A12: End If
releaseHttpLockTask (792)
Run Both Together
A1: Stop [ With Error:Off Task:httpLock ]
<TODO DELETE>
A2: Flash [ Text:%TIMES
Lock released for:
%caller1 Long:On ]
getHttpLockTask (793)
Run Both Together
<TODO DELETE>
A1: [X] Flash [ Text:%TIMES
Requesting lock for:
%caller1 Long:On ]
<whileLoop>
A2: Wait Until [ MS:0 Seconds:3 Minutes:0 Hours:0 Days:0 ] If [ %TRUN !~R ,httpLock, ]
A3: Perform Task [ Name:httpLock Priority:%priority Parameter 1 (%par1): Parameter 2 (%par2): Return Value Variable:%lock Stop:Off ] If [ %TRUN !~R ,httpLock, ]
<TODO DELETE>
A4: [X] Flash [ Text:%TIMES
Lock request FAILURE for:
%caller1
LOCK: %lock Long:On ] If [ %lock neq 1 ]
A5: Goto [ Type:Action Label Number:1 Label:whileLoop ] If [ %lock !Set ]
<TODO DELETE>
A6: [X] Flash [ Text:%TIMES
Lock request SUCCESS for:
%caller1
LOCK: %lock Long:On ]
A7: Return [ Value:%lock Stop:On ]
I cleaned this version up for others. It now calls the lock directly from the 'Query' task. Here is a explanation.
This uses a running task ( httpLock ) for the lock. Instead of setting and checking a global variable this tests the tasker system variable %TRUN. If the task is running then the lock is set.
Query (789)
Run Both Together
A1: Variable Set [ Name:%CfgHttpTimeoutSecs To:30 Do Maths:Off Append:Off ]
// this sets the time out for the lock in case the calling task does not release the lock for some unforseen reason. //
<whileLoop>
A2: Wait Until [ MS:0 Seconds:3 Minutes:0 Hours:0 Days:0 ] If [ %TRUN !~R ,httpLock, ]
// this will loop until the task ' httpLock ' is not running//
A3: Perform Task [ Name:httpLock Priority:%priority Parameter 1 (%par1): Parameter 2 (%par2): Return Value Variable:%http_lock Stop:Off ] If [ %TRUN !~R ,httpLock, ]
// this starts the 'httpLock' task and sets the return variable, only if the task is not running//
A4: Goto [ Type:Action Label Number:1 Label:whileLoop ] If [ %http_lock !Set ]
// this is a handshake from the lock task. Because there is a very slight chance that another task has set the lock in between actions A2 and A3. It checks the return variable is set. If it is not set that means the perform task action has failed and it will start the wait loop again //
A5: Flash [ Text:Test --HTTP GET action Long:On ]
A6: Wait [ MS:0 Seconds:15 Minutes:0 Hours:0 Days:0 ]
// this is to simulate the 'HTTP GET' action. //
A7: Perform Task [ Name:releaseHttpLockTask Priority:%priority Parameter 1 (%par1):%http_lock Parameter 2 (%par2): Return Value Variable: Stop:Off ] If [ %http_lock ~ %Current_Lock ]
// this starts the release task 'only' if %Current_Lock matches the value of this particular task. This test is necessary in case the lock has expired and has been reset by another task IE this will only cancel a lock that was set by this iteration of this task. There are 2 parts to the %Current_Lock. Fist is the %caller ID which identifies the calling task name and second is %TIMEMS at which the lock was set. This is to separate it from concurrent iterations of the same calling task. //
-------------------------
This is the lock task. To get this approach to work it is necessary to have both the child and parent task run together. There is currently a special parent / child relationship that prohibits the parent from running until the child task (of equal or greater priority) is completed (this includes even if the child encounters a wait action). To get around this it is necessary for the child to start another iteration of itself.
httpLock (791)
Abort Existing
A1: Variable Set [ Name:%Current_Lock To:%caller(:)|%TIMEMS Do Maths:Off Append:Off ] If [ %par1 !~ child ]
// This sets %Current_Lock as described above. It will only set on the first instance of the task //
A2: If [ %par1 ~ child ]
// checks to see if this is the second instance of the task and starts the time out wait //
A3: Flash [ Text:Lock set for--
%Current_Lock Long:On ]
A4: Wait [ MS:0 Seconds:%CfgHttpTimeoutSecs + 15 Minutes:0 Hours:0 Days:0 ]
// This is the time out wait. In case the lock is not properly released in x min //
A5: Variable Clear [ Name:%Current_Lock Pattern Matching:Off ]
A6: Flash [ Text:Lock time expired Long:On ]
A7: Else
A8: Return [ Value:%Current_Lock Stop:Off ]
// this returns the lock code to the calling task to confirm the lock and set the lock code in the caller //
A9: Perform Task [ Name:httpLock Priority:%priority - 1 Parameter 1 (%par1):child Parameter 2 (%par2): Return Value Variable: Stop:On ]
// Starts the second iteration of the lock task and stops the first iteration to release the 'parent/ child' condition. **At this point in time there appears to be a bug in taskers task scheduling. This action will only work correctly if it is the last action in the task. This only applys to a task starting another iteration of itself with the collision set to 'abort Existing'** //
A10: End If
// the 'end if' action does not seem to trigger the bug but other actions will //
releaseHttpLockTask (792)
Run Both Together
A1: Stop [ With Error:Off Task:httpLock ]
// stops the lock task which releases the lock //
<TODO DELETE>
A2: Flash [ Text:%TIMES
Lock released for:
%par1 Long:On ]
Unedited descriptions..
Query (789)
Run Both Together
A1: Variable Set [ Name:%CfgHttpTimeoutSecs To:30 Do Maths:Off Append:Off ]
<whileLoop>
A2: Wait Until [ MS:0 Seconds:3 Minutes:0 Hours:0 Days:0 ] If [ %TRUN !~R ,httpLock, ]
A3: Perform Task [ Name:httpLock Priority:%priority Parameter 1 (%par1): Parameter 2 (%par2): Return Value Variable:%http_lock Stop:Off ] If [ %TRUN !~R ,httpLock, ]
A4: Goto [ Type:Action Label Number:1 Label:whileLoop ] If [ %http_lock !Set ]
A5: Flash [ Text:Test --HTTP GET action Long:On ]
A6: Wait [ MS:0 Seconds:15 Minutes:0 Hours:0 Days:0 ]
A7: Perform Task [ Name:releaseHttpLockTask Priority:%priority Parameter 1 (%par1):%http_lock Parameter 2 (%par2): Return Value Variable: Stop:Off ] If [ %http_lock ~ %Current_Lock ]
httpLock (791)
Abort Existing Task
A1: Variable Set [ Name:%Current_Lock To:%caller(:)|%TIMEMS Do Maths:Off Append:Off ] If [ %par1 !~ child ]
A2: If [ %par1 ~ child ]
A3: Flash [ Text:Lock set for--
%Current_Lock Long:On ]
A4: Wait [ MS:0 Seconds:%CfgHttpTimeoutSecs + 15 Minutes:0 Hours:0 Days:0 ]
A5: Variable Clear [ Name:%Current_Lock Pattern Matching:Off ]
A6: Flash [ Text:Lock time expired Long:On ]
A7: Else
A8: Return [ Value:%Current_Lock Stop:Off ]
A9: Perform Task [ Name:httpLock Priority:%priority - 1 Parameter 1 (%par1):child Parameter 2 (%par2): Return Value Variable: Stop:On ]
A10: End If
releaseHttpLockTask (792)
Run Both Together
A1: Stop [ With Error:Off Task:httpLock ]
<TODO DELETE>
A2: Flash [ Text:%TIMES
Lock released for:
%par1 Long:On ]
> Cool. Thanks for coming back to update your idea, Rich D.
It was more of a stubborn need to finish what I started... :)
>
> The original reason I created the getHttpLock and releaseHttpLock task methods was for encapsulation... so that I could change the method implementations easily if I found any problems without affecting the calling task instances.
That makes sense...
BTW.. I noticed the project I posted with the getHttpLock still had the 'httpLock' task collision set to 'run both together'. That should be changed to 'abort existing' .