views:

180

answers:

2

At this point I'm still a noob when it comes to GUI and network programming so I'm hoping this will be a very simple fix. I've got a very basic understanding of the tkinter and asyncore modules having built a handful of programs in each of them, however I'm having trouble using both of them together in a program. I put together an entire UI only to find out that I could not achieve any significant asynchronous networking functionality. For the sake of simplicity, I deconstructed the program into its simplest form to illustrate the basic problem I'm having. Heres the code:

from Tkinter import *
import asyncore, socket

class Application(object):
    def __init__(self, root):
        mainFrame = Frame(root)
        mainFrame.grid(column=1, row=1, columnspan=3, rowspan=1)
        mainButton = Button(mainFrame, text='Click', command=self.makeSocket)
        mainButton.grid(column=2, row=1, columnspan=1, rowspan=1, pady=7, padx=40)

    def makeSocket(self):
        clientSocket()

class clientSocket(asyncore.dispatcher):
    def __init__(self):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect(("XXX.XXX.XXX.XXX", XXXX))
        print 'init works'

    def handle_connect(self):
        print 'connect works'

root = Tk()
myApp = Application(root)
root.after_idle(asyncore.loop) 
root.mainloop()

So when I run the program and click the button, I get the string 'init works', indicating that the clientSocket object is initialized and the connection is made successfully. However, the handle_connect method doesn't run. And if I implement the handle_read method and execute a command on the server(to send data back to the client) this method isn't called either. I'm thinking that there is some general problem that is preventing the asyncore loop from running on its own. I realize that tkinters event loop could be the culprit, but I was under the impression that the after_idle method would allow non-Tkinter events to be processed while the GUI is idle. Is it the tkinter event loop that is still causing problems or could it be something else?

+2  A: 

There are several problems here, and I'm not sure which one it is.

asyncore.loop is a function that never returns, when things are working properly. root.mainloop is probably a function that never returns until you close the window. So things are likely to go wrong because at some point one loop will be starved by the other for some period of time.

(Incidentally, this is why I dislike frameworks that attempt to make their usage easier by replacing the main loop and replacing it with an event-driven system - it works great until you need to use two or more of these systems together, at which point things can get messy.)

However, you can limit the number of times that asyncore.loop will iterate. Try this instead:

def poll_asyncore_once():
    asyncore.loop(count=1)

root.after_idle(poll_asyncore_once) 

You might want to add a timeout value to the loop call too, something less than a second.

However, I would have still thought that the connection would have gone through eventually even if the GUI did end up starved of events as a result of you entering the asyncore loop. This implies something else has gone wrong, and it's possible that asyncore is raising an exception in the connect() method and TK is swallowing it. Try putting an exception handler in clientSocket.init and see how you go.

Kylotan
I added the exception handler and it seems like the connection is happening successfully. I'm gonna look more closely into the recipe linked by Alex below, but out of curiosity, how could I create the program I want without used any even-driven frameworks? If I understand correctly, asyncore is all based on the select() and poll() functions. So would I just need to learn how to use these and create my own loops that are made to fit in with the GUI? And what would I use to make the GUI itself? Would I make it with TK and Tcl or is there a better way to do it?
HoboMo
If you use the count argument to asyncore.loop it effectively becomes a polling system with callbacks rather than an event-driven system, so that side of the problem goes away. I can't comment on Tkinter though - GUI based systems tend to like to be event-based because that matches the typical message queue system they're based on. As long as it makes it convenient for you to use the idle time it's probably ok.
Kylotan
As for your actual problem, I'm not convinced putting anything into a different thread will solve it (though it may be a better solution for other reasons, eg. responsiveness). I'd be tempted to modify my own copy of asyncore to add extra logging (although I seem to recall there are some logging hooks already in there you can use) and find out why the handle_connect isn't getting called.
Kylotan
+1  A: 

See this recipe by Jacob Hallén showing how to use asyncore and Tkinter together (basically by means of a threading trick). (It's also expanded as recipe 9.6 in the Python Cookbook's first printed edition, and as 11.4 in the second edition).

Alex Martelli