My Tk app has many "wait" windows or pauses in a functions that allow time for other backgrounds commands to do their job. The problem is that using "after 5000" within a function disables all the buttons in the application. I've found a lot of information, the most helpful was at http://wiki.tcl.tk/808. First lesson learned is that "after" without a script won't process the event loop, and second is that vwaits are nested.
So, I use the following simple "pause" function in place of "after":
proc pause {ms {waitvar WAITVAR}} {
global $waitvar
after $ms "set $waitvar 1"
puts "waiting $ms for $waitvar"
vwait $waitvar
puts "pause $ms returned"
}
button .b -text PressMe -command {pause 5000 but[incr i]}; # everyone waits on this
pack .b
after 0 {pause 1000 var1}; pause 3000 var2; # works as expected
after 0 {pause 3000 var3}; pause 1000 var4; # both return after 3 secs
My button is always responsive, but if pressed, all other vwaits are held up for at least another 5 seconds. And a second press within 5 seconds also delays the first one. Understanding that vwaits are nested, this is now expected and not really problematic.
This seems almost too simple a solution, so I'd like to get comments as to what issues I might not have though of.
You've listed the main issue, that a vwait call will merrily nest inside another vwait call. (They're implemented using a recursive call to the event loop engine, so that's to be expected.) This can be a particular problem when you get something that ends up nesting inside itself; you can blow up the stack this way very easily. The traditional way of fixing this is with careful interlocking, such as disabling the button that invokes this particular callback while you're processing the vwait; that also gives quite a good way to indicate to the user that you're busy.
The other approach (which you might well still use with the button disabling) is to break up the code so that instead of:
proc callback {} {
puts "do some stuff 1"
pause 5000
puts "do some stuff 2"
}
You instead do:
proc callback {} {
puts "do some stuff 1"
after 5000 callback2
}
proc callback2 {} {
puts "do some stuff 2"
}
This allows you to avoid the vwait itself. It's called continuation-passing style programming, and it's pretty common in high-quality Tcl code. It does get a bit tricky though. Consider this looping version:
proc callback {} {
for {set i 1} {$i <= 5} {incr i} {
puts "This is iteration $i"
pause 1000
}
puts "all done"
}
In continuation-passing style, you'd do something like this:
proc callback {{i 1}} {
if {$i <= 5} {
puts "This is iteration $i"
after 1000 [list callback [incr i]]
} else {
puts "all done"
}
}
The more local state you've got, the trickier it is to transform the code!
With Tcl/Tk 8.6 you've got some extra techniques.
Firstly, you can use a coroutine to simplify that tricky continuation-passing stuff.
proc callback {} {
coroutine c[incr ::coroutines] apply {{} {
for {set i 1} {$i <= 5} {incr i} {
puts "This is iteration $i"
after 1000 [info coroutine]
yield
}
puts "all done"
}}
}
This is a bit longer, but is much easier as the size and complexity of the state increases.
The other new 8.6 facility is the tk busy command, which can be used to make convenient modal dialogs that you can't interact with while some operation is happening (via clever tricks with invisible windows). It's still up to your code to ensure that the user is told that things are busy, again by marking things disabled, etc., but tk busy can make it much easier to implement (and can help avoid the nest of little tricky problems with grab).
Related
I am writing a autologin script in Powershell. With main purpose of doing autologon with keystrokes on remote clients in our environment after installation, with the desired AD and password entered.
Works fine on my i9. But most people using Tablets and Elitebooks so using
Thread Sleep
Works bad since i would need to have custom timing on Every hardware, or very high default numbers for lower end clients using my script
Is there any way adding an "wait for row above to completed" Before continuation to next.
I don't have enough on your current code to produce a more accurate answer but the idea, in all cases, remains the same.
You should periodically wake up the thread to check whether or not the machine is in the state you want it in and from there, you either go back to sleep or exit the loop and continue.
The delay is up to you but you want to find a sweet spot to have great performance and reactivity.
Example (based on your description)
$IsLoggedIn = $false
while (! $IsLoggedIn) {
$IsLoggedIn = 'Custom Logic returning $true if the user is logged in'
if ($IsLoggedIn) { break }
Start-Sleep -Milliseconds 100
}
You just need to figure out the thing you want to use as the check to validate the computer is in the correct state you need it in before proceeding further.
I have the model which I posted before on Stack. I am currently running the iterations through 5 Flow Chart blocks contain enter block and service block. when agent fill service block 5 in flow chart 5, the exit block should start to fill block one and so on. I have used While infinite loop to loop between the five flow chart blocks but it isn't working.
while(true)
{
for (Curing_Drying currProcess : collection) {
if (currProcess.allowedDay == (int)time(DAY)) {
currProcess.enter.take(agent);
}
}
if (queue10.size() <= Throughtput1){
break;
}
}
Image for further illustration 1
Image for further illustration 2
Wondering if someone can tell me what is wrong in the code.
Based on the description and the pictures provided, it isn't clear why the while loop is necessary. The On exit action is executed for each Agent arrival to the Exit block. It seems that the intention is to find the appropriate Curing_Drying block based on number of days since the model start time? If so, then just iterating through the collection is enough.
Also, it is generally a good practice to provide more meaningful names to collections. Using simply collection doesn't say anything about the contents and can get pretty confusing later on.
I have a program like this
...
$self->{bn1}=Wx::Button->new( $tb1, -1, 'Start');
EVT_BUTTON( $self, $self->{bn1}, \&on_click_start );
...
...
sub on_click_start
{
my( $this, $event ) = #_;
$this->{bn1}->SetLabel("Cancel");
$event->Skip;
for (...) {
long_time_operation();
last if ( Cancel_clicked );
}
}
...
My problem is when I click the Start button, on_click_start() will be called, and I want change the label of Start button to Cancel, that allow I to click the button to break out the long_time_operation() loop.
How do I make right code for it?
The only real solution is to use multiple threads and perform the long operation in a background thread. If you want to keep your code simple, you can use wxYield() to handle the events from inside this event handler, but be aware that this may (and will) result in difficult to debug problems due to reentrancy, so at the very least you need to disable the rest of your UI if you're doing it like this.
Try with a wxTimer and start it in your on_click_start function. Put the long time operation code under the event of the timer (ensure that it dont trigger repeteadly, just stop the timer there too).
Inside your long operation function use some global var to know if you want to cancel. In the event of your button now change the value of your global var so your long term code knows about that and cancels/break from the loop.
I am not sure if a loop under the event of the timer can hang your UI, but it appears to use threads, so that may not happen. Try it anyway, i always use wxTimer when I need something like that (dont hanging the UI and not using threads directly).
Is there a possibility to wait for an process to quit, without it needs to running?
I know there is the keyword WaitForExit, but to use this the process needs to run.
My second question is, if there is a possibility to use an else-Statement in an while loop.
Tried it already, but it always said that there isnt an function called else.
Do Until
Do {
Sleep 5
} Until (Get-Process iexplore);
Will wait until iexplore is found
While
While (Get-Process iexplore) {
Sleep 5
}
Will wait until iexplore is no longer running
Else after while
You cannot use an else statement after a while loop.
It needs to come after an if.
if there is a possibility to use an else-Statement in an while loop.
If you mean something like:
while (cond) {
} else {
}
?
Then NO. (how would the content of the else block be any different to code immediately following the while block?)
Is there a possibility to wait for an process to quit,
Yes. There are different ways of doing this, depending on the nature of the target process. Is it one created by the same script? Is the same session? A service? Or just an arbitrary process?
Does it seems to fits your needs (1st question) ? http://technet.microsoft.com/library/hh849813.aspx
You can use wait-process cmdlet.
Check link for details http://ss64.com/ps/wait-process.html
Example: wait-process -name notepad.exe
I have the following auto-responder on my bot
on *:TEXT:*sparky*:*: { msg # $read(scripts/name-responses.txt) }
on *:ACTION:*sparky*:*: { msg # $read(scripts/name-responses.txt) }
I wanted to know how can I tell write a code, I'm guessing with an IF statement, that if a user types sparky more than twice that the user gets ignored for 120 seconds. This way, my bot doesn't flood the chat due to the auto-responder feature.
Any help would be appreciated!
I would recommend keeping track of all users that have used the command, and when they have last used it. This can easily be done by saving all data in an INI file.
You can save this information by using the writeini command. To write the data to this file, use something along the lines of the following:
writeini sparky.ini usage $nick $ctime
$ctime will evaluate to the number of seconds elapsed since 1970/01/01. This is generally the way to compare times of events.
Once a user triggers your script again, you can read the value from this INI file and compare it to the current time. If the difference between the times is less than 10 seconds (for example), it can send the command and then ignore them for 120 seconds. You would read the value of their last usage using:
$readini(sparky.ini, n, usage, $nick)
Your final script could look like something along the lines of the following script. I've moved the functionality to a separate alias (/triggerSparky <nick> <channel>) to avoid identical code in the on TEXT and on ACTION event listeners.
on *:TEXT:*sparky*:#: {
triggerSparky
}
on *:ACTION:*sparky*:#: {
triggerSparky
}
alias triggerSparky {
; Send the message
msg $chan $read(scripts/name-responses.txt, n)
if ($calc($ctime - $readini(sparky.ini, n, usage, $nick)) < 10) {
; This user has recently triggered this script (10 seconds ago), ignore him for 120 seconds
ignore -u120 $nick
remini sparky.ini usage $nick
}
else {
writeini sparky.ini usage %nick $ctime
}
}
Of course, a slightly easier way to achieve a similar result is by simply ignoring them for a predefined time without saving their data in an INI file. This would stop you from checking whether they have triggered twice recently, but it would be a good way to only allow them to trigger it once per two minutes, for example.