Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #99629 > unrolled thread
| Started by | jfong@ms4.hinet.net |
|---|---|
| First post | 2015-11-26 23:51 -0800 |
| Last post | 2015-11-27 13:49 +0100 |
| Articles | 13 — 3 participants |
Back to article view | Back to comp.lang.python
python response slow when running external DLL jfong@ms4.hinet.net - 2015-11-26 23:51 -0800
Re: python response slow when running external DLL Peter Otten <__peter__@web.de> - 2015-11-27 10:18 +0100
Re: python response slow when running external DLL jfong@ms4.hinet.net - 2015-11-27 03:14 -0800
Re: python response slow when running external DLL Peter Otten <__peter__@web.de> - 2015-11-27 13:20 +0100
Re: python response slow when running external DLL jfong@ms4.hinet.net - 2015-11-27 23:37 -0800
Re: python response slow when running external DLL Peter Otten <__peter__@web.de> - 2015-11-28 11:13 +0100
Re: python response slow when running external DLL jfong@ms4.hinet.net - 2015-11-28 18:55 -0800
Re: python response slow when running external DLL jfong@ms4.hinet.net - 2015-11-30 17:03 -0800
Re: python response slow when running external DLL Peter Otten <__peter__@web.de> - 2015-12-01 12:01 +0100
Re: python response slow when running external DLL jfong@ms4.hinet.net - 2015-12-01 19:58 -0800
Re: python response slow when running external DLL Laura Creighton <lac@openend.se> - 2015-11-28 11:51 +0100
Re: python response slow when running external DLL jfong@ms4.hinet.net - 2015-11-28 19:04 -0800
Re: python response slow when running external DLL Laura Creighton <lac@openend.se> - 2015-11-27 13:49 +0100
| From | jfong@ms4.hinet.net |
|---|---|
| Date | 2015-11-26 23:51 -0800 |
| Subject | python response slow when running external DLL |
| Message-ID | <dc290806-c537-4546-b802-88dff14d81c0@googlegroups.com> |
I am new to Python. As an exercise of it, I try to port a program which was written more than 10 years ago. This program use the Borland C++ Builder as its GUI front end and a DLL does the real work(it will takes a few seconds to complete). I saw a strange phenomenon in the following codes. The "var_status.set('Download...')" statement seems was deferred and didn't show up until the external DLL job was finished and I can only saw the result of "var_status.set('Download OK')" statement which immediately follows. I try to do the DLL function in a thread(selected by using the "test" flag in codes), but it didn't help.
Can anyone tell me what's the point I was missed?
-------------------------
def download():
global iniFilename
if test: global result, busy
ini = iniFilename
iniFilename = "c:\\$$temp.in3"
saveIniFile()
iniFilename = ini
#do the rest
var_status.set('Download...')
if not test:
result = mydll.SayHello()
else:
busy = True
_thread.start_new_thread(td_download, ())
while busy: pass
if result:
var_status.set("Download Fail at %s" % hex(result))
showerror('Romter', 'Download Fail')
else:
var_status.set('Download OK')
showinfo('Romter', 'Download OK')
if test:
result = 0x5555
busy = True
def td_download():
global busy, result
result = mydll.SayHello()
busy = False
--------------------------
Best Regards,
Jach
[toc] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2015-11-27 10:18 +0100 |
| Message-ID | <mailman.169.1448615930.20593.python-list@python.org> |
| In reply to | #99629 |
jfong@ms4.hinet.net wrote:
> I am new to Python. As an exercise of it, I try to port a program which
> was written more than 10 years ago. This program use the Borland C++
> Builder as its GUI front end and a DLL does the real work(it will takes a
> few seconds to complete). I saw a strange phenomenon in the following
> codes. The "var_status.set('Download...')" statement seems was deferred
> and didn't show up until the external DLL job was finished and I can only
> saw the result of "var_status.set('Download OK')" statement which
> immediately follows. I try to do the DLL function in a thread(selected by
> using the "test" flag in codes), but it didn't help.
>
> Can anyone tell me what's the point I was missed?
What does var_status.set() do? If it writes to stdout you may just need to
flush().
Do you see the same behaviour when you replace mydll.SayHello() with
something simple like time.sleep(1)?
> -------------------------
> def download():
> global iniFilename
> if test: global result, busy
> ini = iniFilename
> iniFilename = "c:\\$$temp.in3"
> saveIniFile()
> iniFilename = ini
> #do the rest
> var_status.set('Download...')
> if not test:
> result = mydll.SayHello()
> else:
> busy = True
> _thread.start_new_thread(td_download, ())
> while busy: pass
> if result:
> var_status.set("Download Fail at %s" % hex(result))
> showerror('Romter', 'Download Fail')
> else:
> var_status.set('Download OK')
> showinfo('Romter', 'Download OK')
>
> if test:
> result = 0x5555
> busy = True
> def td_download():
> global busy, result
> result = mydll.SayHello()
> busy = False
> --------------------------
As a general remark keep your test scripts as simple as possible. Example:
If
import mydll
print("Download...", end="")
mydll.SayHello()
print("OK")
showed the same behaviour it would be the ideal test script.
[toc] | [prev] | [next] | [standalone]
| From | jfong@ms4.hinet.net |
|---|---|
| Date | 2015-11-27 03:14 -0800 |
| Message-ID | <f4cf8b4f-94e7-4d97-b144-65f59c20fbc1@googlegroups.com> |
| In reply to | #99630 |
Peter Otten at 2015/11/27 UTC+8 5:19:17 PM wrote:
Hi! Peter, thanks for your prompt reply.
> What does var_status.set() do? If it writes to stdout you may just need to
> flush().
var_status is a StringVar which binds to a lable's textvariable. I use this label as the status bar to show message.
> Do you see the same behaviour when you replace mydll.SayHello() with
> something simple like time.sleep(1)?
I use time.sleep(3) to replace mydll.SayHello(), still get the same.
> As a general remark keep your test scripts as simple as possible. Example:
> If
>
> import mydll
> print("Download...", end="")
> mydll.SayHello()
> print("OK")
>
> showed the same behaviour it would be the ideal test script.
I run the above statements in a test file, it works as expected. I can see "Download..." and then "OK".
[toc] | [prev] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2015-11-27 13:20 +0100 |
| Message-ID | <mailman.173.1448626828.20593.python-list@python.org> |
| In reply to | #99636 |
jfong@ms4.hinet.net wrote:
> Peter Otten at 2015/11/27 UTC+8 5:19:17 PM wrote:
>
> Hi! Peter, thanks for your prompt reply.
>
>> What does var_status.set() do? If it writes to stdout you may just need
>> to flush().
>
> var_status is a StringVar which binds to a lable's textvariable. I use
> this label as the status bar to show message.
Can I conclude from the above that you are using tkinter? Widgets can only
update when the event loop gets a chance to run. While a quick fix is to
invoke update_idletasks() callbacks shouldn't generally contain long-running
code that -- as you have seen -- will block the complete user interface.
>> Do you see the same behaviour when you replace mydll.SayHello() with
>> something simple like time.sleep(1)?
>
> I use time.sleep(3) to replace mydll.SayHello(), still get the same.
>
>> As a general remark keep your test scripts as simple as possible.
>> Example: If
>>
>> import mydll
>> print("Download...", end="")
>> mydll.SayHello()
>> print("OK")
>>
>> showed the same behaviour it would be the ideal test script.
>
> I run the above statements in a test file, it works as expected. I can
> see "Download..." and then "OK".
>
>
Quick-fix example:
#!/usr/bin/env python3
import tkinter as tk
import time
def download():
var.set("Starting download...")
root.update_idletasks()
time.sleep(3)
var.set("... done")
root = tk.Tk()
var = tk.StringVar()
label = tk.Label(root, textvariable=var)
label.pack()
button = tk.Button(root, text="Download", command=download)
button.pack()
root.mainloop()
A cleaner solution can indeed involve threads; you might adapt the approach
from <http://effbot.org/zone/tkinter-threads.htm> (Python 2 code).
[toc] | [prev] | [next] | [standalone]
| From | jfong@ms4.hinet.net |
|---|---|
| Date | 2015-11-27 23:37 -0800 |
| Message-ID | <d6c59a36-38ba-4f26-b949-18f2b226e7e3@googlegroups.com> |
| In reply to | #99638 |
Peter Otten at 2015/11/27 UTC+8 8:20:54PM wrote:
> Quick-fix example:
> def download():
> var.set("Starting download...")
> root.update_idletasks()
> time.sleep(3)
> var.set("... done")
Thanks, Peter, The update_idletasks() works. In my trivial program it's easy to apply for there are only two places call the DLL function.
> A cleaner solution can indeed involve threads; you might adapt the approach
> from <http://effbot.org/zone/tkinter-threads.htm> (Python 2 code).
Using thread is obviously more logical. I think my mistake was the "while busy: pass" loop which makes no sense because it blocks the main thread, just as the time.sleep() does. That's why in your link (and Laura's too) the widget.after() scheduling was used for this purpose.
From what I had learned here, the other way I can do is making the codes modified as follows. It will get ride of the "result" and "busy" global variables, but it also makes the codes looks a little ugly. I think I will take the update_idletasks() way in this porting for it seems more simpler, and can be used on thread or non-thread calling. Thank you again.
.....
.....
#do the rest
var_status.set('Download...')
_thread.start_new_thread(td_download, ()) #must use threading
def td_download():
result = mydll.SayHello()
if result:
var_status.set("Download Fail at %s" % hex(result))
showerror('Romter', 'Download Fail')
else:
var_status.set('Download OK')
showinfo('Romter', 'Download OK')
[toc] | [prev] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2015-11-28 11:13 +0100 |
| Message-ID | <mailman.184.1448705627.20593.python-list@python.org> |
| In reply to | #99655 |
jfong@ms4.hinet.net wrote:
> Peter Otten at 2015/11/27 UTC+8 8:20:54PM wrote:
>
>> Quick-fix example:
>> def download():
>> var.set("Starting download...")
>> root.update_idletasks()
>> time.sleep(3)
>> var.set("... done")
>
> Thanks, Peter, The update_idletasks() works. In my trivial program it's
> easy to apply for there are only two places call the DLL function.
>
>> A cleaner solution can indeed involve threads; you might adapt the
>> approach from <http://effbot.org/zone/tkinter-threads.htm> (Python 2
>> code).
>
> Using thread is obviously more logical. I think my mistake was the "while
> busy: pass" loop which makes no sense because it blocks the main thread,
> just as the time.sleep() does. That's why in your link (and Laura's too)
> the widget.after() scheduling was used for this purpose.
No, the point of both recipes is that tkinter operations are only ever
invoked from the main thread. The main thread has polling code that
repeatedly looks if there are results from the helper thread. As far I
understand the polling method has the structure
f():
# did we get something back from the other thread?
# a queue is used to avoid race conditions
# if yes react.
# var_status.set() goes here
# reschedule f to run again in a few millisecs;
# that's what after() does
> From what I had learned here, the other way I can do is making the codes
> modified as follows. It will get ride of the "result" and "busy" global
> variables, but it also makes the codes looks a little ugly. I think I will
> take the update_idletasks() way in this porting for it seems more simpler,
> and can be used on thread or non-thread calling. Thank you again.
> .....
> .....
> #do the rest
> var_status.set('Download...')
> _thread.start_new_thread(td_download, ()) #must use threading
>
> def td_download():
> result = mydll.SayHello()
> if result:
> var_status.set("Download Fail at %s" % hex(result))
> showerror('Romter', 'Download Fail')
> else:
> var_status.set('Download OK')
> showinfo('Romter', 'Download OK')
As td_download() runs in the other thread the var_status.set() methods are
problematic.
Another complication that inevitably comes with concurrency: what if the
user triggers another download while one download is already running? If you
don't keep track of all downloads the message will already switch to
"Download OK" while one download is still running.
[toc] | [prev] | [next] | [standalone]
| From | jfong@ms4.hinet.net |
|---|---|
| Date | 2015-11-28 18:55 -0800 |
| Message-ID | <3ec6a016-dae5-4986-b035-c1a8cd4dc3e7@googlegroups.com> |
| In reply to | #99656 |
Peter Otten at 2015/11/28 UTC+8 6:14:09PM wrote:
> No, the point of both recipes is that tkinter operations are only ever
> invoked from the main thread. The main thread has polling code that
> repeatedly looks if there are results from the helper thread. As far I
> understand the polling method has the structure
>
> f():
> # did we get something back from the other thread?
> # a queue is used to avoid race conditions
>
> # if yes react.
> # var_status.set() goes here
>
> # reschedule f to run again in a few millisecs;
> # that's what after() does
Have no idea how the main thread poll on all those events (or it use a queue)?
All I know now is that the main thread(mainloop()?) can be easily blocked by event handlers if the handler didn't run as a separate thread.
> > .....
> > .....
> > #do the rest
> > var_status.set('Download...')
> > _thread.start_new_thread(td_download, ()) #must use threading
> >
> > def td_download():
> > result = mydll.SayHello()
> > if result:
> > var_status.set("Download Fail at %s" % hex(result))
> > showerror('Romter', 'Download Fail')
> > else:
> > var_status.set('Download OK')
> > showinfo('Romter', 'Download OK')
>
> As td_download() runs in the other thread the var_status.set() methods are
> problematic.
No idea what kind of problem it will encounter. Can you explain?
> Another complication that inevitably comes with concurrency: what if the
> user triggers another download while one download is already running? If you
> don't keep track of all downloads the message will already switch to
> "Download OK" while one download is still running.
Hummm...this thought never comes to my mind. After take a quick test I found, you are right, a second "download" was triggered immediately. That's a shock to me. I suppose the same event shouldn't be triggered again, or at least not triggered immediately, before its previous handler was completed. ...I will take a check later on Borland C++ builder to see how it reacts!
Anyway to prevent this happens? if Python didn't take care it for us.
[toc] | [prev] | [next] | [standalone]
| From | jfong@ms4.hinet.net |
|---|---|
| Date | 2015-11-30 17:03 -0800 |
| Message-ID | <1450e163-23f7-47b7-8e8d-40d304ea003f@googlegroups.com> |
| In reply to | #99685 |
jf...@ms4.hinet.net at 2015/11/29 UTC+8 10:55:28AM wrote:
> > > .....
> > > .....
> > > #do the rest
> > > var_status.set('Download...')
> > > _thread.start_new_thread(td_download, ()) #must use threading
> > >
> > > def td_download():
> > > result = mydll.SayHello()
> > > if result:
> > > var_status.set("Download Fail at %s" % hex(result))
> > > showerror('Romter', 'Download Fail')
> > > else:
> > > var_status.set('Download OK')
> > > showinfo('Romter', 'Download OK')
> >
> > As td_download() runs in the other thread the var_status.set() methods are
> > problematic.
>
> No idea what kind of problem it will encounter. Can you explain?
It might be not a good idea to update GUI in a spawned thread, according to Mark Lutz's book "Programming Python", section "Using Threads with tkinter GUIs"
"If you do use threads in tkinter programs, however, you need to remember that only the main thread (the one that built the GUI and started the mainloop) should generally make GUI calls. At the least, multiple threads should not attempt to update the GUI at the same time..."
> > Another complication that inevitably comes with concurrency: what if the
> > user triggers another download while one download is already running? If you
> > don't keep track of all downloads the message will already switch to
> > "Download OK" while one download is still running.
>
> Hummm...this thought never comes to my mind. After take a quick test I found, you are right, a second "download" was triggered immediately. That's a shock to me. I suppose the same event shouldn't be triggered again, or at least not triggered immediately, before its previous handler was completed. ...I will take a check later on Borland C++ builder to see how it reacts!
>
> Anyway to prevent this happens? if Python didn't take care it for us.
then don't use thread if you prefer the GUI freeze.
[toc] | [prev] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2015-12-01 12:01 +0100 |
| Message-ID | <mailman.66.1448967701.14615.python-list@python.org> |
| In reply to | #99685 |
jfong@ms4.hinet.net wrote:
> Peter Otten at 2015/11/28 UTC+8 6:14:09PM wrote:
>> No, the point of both recipes is that tkinter operations are only ever
>> invoked from the main thread. The main thread has polling code that
>> repeatedly looks if there are results from the helper thread. As far I
>> understand the polling method has the structure
>>
>> f():
>> # did we get something back from the other thread?
>> # a queue is used to avoid race conditions
>>
>> # if yes react.
>> # var_status.set() goes here
>>
>> # reschedule f to run again in a few millisecs;
>> # that's what after() does
>
> Have no idea how the main thread poll on all those events (or it use a
> queue)? All I know now is that the main thread(mainloop()?) can be easily
> blocked by event handlers if the handler didn't run as a separate thread.
>
>> > .....
>> > .....
>> > #do the rest
>> > var_status.set('Download...')
>> > _thread.start_new_thread(td_download, ()) #must use threading
>> >
>> > def td_download():
>> > result = mydll.SayHello()
>> > if result:
>> > var_status.set("Download Fail at %s" % hex(result))
>> > showerror('Romter', 'Download Fail')
>> > else:
>> > var_status.set('Download OK')
>> > showinfo('Romter', 'Download OK')
>>
>> As td_download() runs in the other thread the var_status.set() methods
>> are problematic.
>
> No idea what kind of problem it will encounter. Can you explain?
While the var_status.set() invoked from the second thread modifies some
internal data the main thread could kick in and modify (parts of) that same
data, thus bringing tkinter into an broken state. A simple example that
demonstrates the problem:
import random
import threading
import time
account = 70
def withdraw(delay, amount):
global account
if account >= amount:
print("withdrawing", amount)
account -= amount
else:
print("failed to withdraw", amount)
threads = []
for i in range(10):
t = threading.Thread(
target=withdraw,
kwargs=dict(delay=.1,
amount=random.randrange(1, 20)))
threads.append(t)
t.start()
for t in threads:
t.join()
Before every withdrawal there seems to be a check that ensures that there is
enough money left, but when you run the script a few times you will still
sometimes end with a negative balance. That happens when thread A finds
enough money, then execution switches to thread B which also finds enough
money, then both threads perform a withdrawal -- oops there wasn't enough
money for both.
>> Another complication that inevitably comes with concurrency: what if the
>> user triggers another download while one download is already running? If
>> you don't keep track of all downloads the message will already switch to
>> "Download OK" while one download is still running.
>
> Hummm...this thought never comes to my mind. After take a quick test I
> found, you are right, a second "download" was triggered immediately.
> That's a shock to me. I suppose the same event shouldn't be triggered
> again, or at least not triggered immediately, before its previous handler
> was completed. ...I will take a check later on Borland C++ builder to see
> how it reacts!
>
> Anyway to prevent this happens? if Python didn't take care it for us.
A simple measure would be to disable the button until the download has
ended.
[toc] | [prev] | [next] | [standalone]
| From | jfong@ms4.hinet.net |
|---|---|
| Date | 2015-12-01 19:58 -0800 |
| Message-ID | <78fd0a4e-50d0-49fd-8a36-94e376e45fc9@googlegroups.com> |
| In reply to | #99789 |
Peter Otten at 2015/12/1 UTC+8 7:01:55PM wrote:
> While the var_status.set() invoked from the second thread modifies some
> internal data the main thread could kick in and modify (parts of) that same
> data, thus bringing tkinter into an broken state. A simple example that
> demonstrates the problem:
>
> import random
> import threading
> import time
>
> account = 70
>
> def withdraw(delay, amount):
> global account
> if account >= amount:
> print("withdrawing", amount)
> account -= amount
> else:
> print("failed to withdraw", amount)
>
> threads = []
> for i in range(10):
> t = threading.Thread(
> target=withdraw,
> kwargs=dict(delay=.1,
> amount=random.randrange(1, 20)))
> threads.append(t)
> t.start()
>
> for t in threads:
> t.join()
It's a simple and vivid example. You must be in the banking business:-)
In this exercise, I can just use update_idletasks() method to solve the deferred display problem of tkinter, and forget all about thread. But what if I really want to use thread for the long-running DLL function in the download hander? I didn't figure out a correct way of doing it at this moment. Maybe queue is the answer but I am not familiar with it yet.
def download():
var_status.set(...) #showing a start-up message
result = SayHello() #this is a long-running function
if result.... #according to the result, display more messages
....
>
> Before every withdrawal there seems to be a check that ensures that there is
> enough money left, but when you run the script a few times you will still
> sometimes end with a negative balance. That happens when thread A finds
> enough money, then execution switches to thread B which also finds enough
> money, then both threads perform a withdrawal -- oops there wasn't enough
> money for both.
>
> >> Another complication that inevitably comes with concurrency: what if the
> >> user triggers another download while one download is already running? If
> >> you don't keep track of all downloads the message will already switch to
> >> "Download OK" while one download is still running.
> >
> > Hummm...this thought never comes to my mind. After take a quick test I
> > found, you are right, a second "download" was triggered immediately.
> > That's a shock to me. I suppose the same event shouldn't be triggered
> > again, or at least not triggered immediately, before its previous handler
> > was completed. ...I will take a check later on Borland C++ builder to see
> > how it reacts!
> >
> > Anyway to prevent this happens? if Python didn't take care it for us.
>
> A simple measure would be to disable the button until the download has
> ended.
Good! simple and work.
I shouldn't say "Python should take care of it", but "tk" should do. It's annoying the widget's command was re-triggered before it was finished. (my personal opinion:-)
[toc] | [prev] | [next] | [standalone]
| From | Laura Creighton <lac@openend.se> |
|---|---|
| Date | 2015-11-28 11:51 +0100 |
| Message-ID | <mailman.185.1448707926.20593.python-list@python.org> |
| In reply to | #99655 |
In a message of Sat, 28 Nov 2015 11:13:38 +0100, Peter Otten writes: >jfong@ms4.hinet.net wrote: >> Using thread is obviously more logical. I think my mistake was the "while >> busy: pass" loop which makes no sense because it blocks the main thread, >> just as the time.sleep() does. That's why in your link (and Laura's too) >> the widget.after() scheduling was used for this purpose. I never saw the reply that Peter is replying to. The threading module constructs a higher level interface on top of the low level thread module. Thus it is the preferred way to go for standard Python code -- and even Fredrik's recipe contains the line: import thread # should use the threading module instead! Laura
[toc] | [prev] | [next] | [standalone]
| From | jfong@ms4.hinet.net |
|---|---|
| Date | 2015-11-28 19:04 -0800 |
| Message-ID | <3ee1f8f1-afaa-4610-a942-f6cdef61995e@googlegroups.com> |
| In reply to | #99658 |
Laura Creighton at 2015/11/28 UTC+8 6:52:25PM wrote: > I never saw the reply that Peter is replying to. > The threading module constructs a higher level interface on top of the > low level thread module. Thus it is the preferred way to go for > standard Python code -- and even Fredrik's recipe contains the > line: > import thread # should use the threading module instead! > > Laura Hi! Laura, takes the porting of an old BCB GUI program as an exercise in my learning python, I just quickly grab the required tools (mainly the tkinter) in python to complete this "work" and get a feeling of how python performs on doing this. Most of my knowledge of python comes from Mark Lutz's book "Learning python" and "Programming python". I didn't dive into this language deeply yet. There are topics about "_thread" and "threading" modules in the book and I just pick the lower level one for this because the "threading" is based on the "_thread". Thanks for your note and link. --Jach
[toc] | [prev] | [next] | [standalone]
| From | Laura Creighton <lac@openend.se> |
|---|---|
| Date | 2015-11-27 13:49 +0100 |
| Message-ID | <mailman.174.1448628595.20593.python-list@python.org> |
| In reply to | #99636 |
In a message of Fri, 27 Nov 2015 13:20:03 +0100, Peter Otten writes: >A cleaner solution can indeed involve threads; you might adapt the approach >from <http://effbot.org/zone/tkinter-threads.htm> (Python 2 code). But it is probably better to use threading http://code.activestate.com/recipes/82965-threads-tkinter-and-asynchronous-io/ Laura
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.python
csiph-web