0

我正在创建一个小脚本,用于检查我的 gmail 帐户中的邮件数量并在状态栏中打印它们。函数 gmail() 返回新电子邮件的数量。我有几个问题,但首先这是我到目前为止写的代码(显然我是新手):

class MyApplicationAppDelegate(NSObject):

var = 1

def applicationDidFinishLaunching_(self, sender):
    NSLog("Application did finish launching.")

    global ngmail

    self.statusItem = NSStatusBar.systemStatusBar().statusItemWithLength_(NSVariableStatusItemLength)

    while var == 1 :  
        ngmail2 = gmail();
        if  ngmail2 !=ngmail:
            self.statusItem.setTitle_("loading")
            self.statusItem.setTitle_(ngmail2)
            ngmail = ngmail2
        time.sleep(6)

1) 为什么我需要 "self.statusItem.setTitle_("loading")" 行?没有那条线,它不会自我更新。我真的不知道为什么。

2)它运行正常,但每当我接近状态栏中的数字时,就会出现纺车。
我想原因是因为我正在使用 while,而我应该使用类似 nsrunloop 或类似的东西。有人可以就此提出建议吗?

3)如果我让我的 mac 进入睡眠状态并唤醒它,脚本将停止工作。有什么解决办法吗?也许这与上面的问题2)有关。

谢谢!

4

1 回答 1

0

您所有的问题都来自您阻塞主线程的事实。

在 Cocoa 或几乎任何其他 GUI 框架中,主线程运行一个循环,等待下一个事件,调用事件处理程序,并重复直到退出。

您的事件处理程序applicationDidFinishLaunching_,永远不会返回。这意味着 Cocoa 永远无法处理下一个事件。最终,操作系统会注意到您没有响应并放置沙滩球。

使用 Cocoa,有时它会在每次你给它机会时潜入其他事件,比如在setTitle_通话中,即使你没有响应,操作系统也会伪造一些东西,比如保持窗口重绘,所以它不是您的应用程序没有响应总是很明显。但这并不意味着您不需要解决问题。

有很多方法可以做到这一点,但最简单的可能是使用后台线程。然后,applicationDidFinishLaunching_ 可以启动后台线程,然后立即返回,允许主线程返回其作业处理事件。

唯一棘手的一点是在后台线程上运行的代码无法调用 UI 对象。那么,你会怎么做呢?

performSelectorOnMainThread_withObject_waitUntilDone_就是为了。


这是一个例子:

class MyApplicationAppDelegate(NSObject):

    var = 1

    def background_work(self):
        global ngmail

        while var == 1 :  
            ngmail2 = gmail();
            if  ngmail2 !=ngmail:
                self.statusItem.setTitle_("loading")
                self.statusItem.performSelectorOnMainThread_withObject_waitUntilDone_('setTitle:', ngmail2, False)
            time.sleep(6)

    def applicationDidFinishLaunching_(self, sender):
        NSLog("Application did finish launching.")
        self.statusItem = NSStatusBar.systemStatusBar().statusItemWithLength_(NSVariableStatusItemLength)
        self.background_worker = threading.Thread(target=self.background_work)
        self.background_worker.start()    

唯一棘手的一点是您必须为选择器使用 ObjC 名称 ( setTitle:),而不是 Python 名称 ( setTitle_)。


但是,您的代码还有另一个微妙的错误:var实际上并没有同步,因此您可以在主线程中更改它的值,而不会注意到后台线程。

最重要的是,执行 asleep(6)意味着退出您的应用程序最多需要 6 秒,因为后台线程在var完成睡眠之前不会访问检查代码。

您可以使用Condition.

class MyApplicationAppDelegate(NSObject):

    var = 1
    condition = threading.Condition()

    def background_work(self):
        global ngmail

        with condition:
            while var == 1:
                ngmail2 = gmail();
                if ngmail2 != ngmail:
                    self.statusItem.performSelectorOnMainThread_withObject_waitUntilDone_('setTitle:', ngmail2, False)
                condition.wait(6)

    @classmethod
    def shutdown_background_threads(cls):
        with condition:
            var = 0
            condition.notify_all()

(我假设您故意使用类属性var而不是实例属性,所以我同样将条件设为类属性,将关闭方法设为类方法。)

于 2013-07-02T21:11:31.697 回答