在OS X上继续进行 – 两个库调用系统函数

huangapple go评论99阅读模式
英文:

go on OS X - Two libraries call system functions

问题

我发现至少对于Mac来说,编写具有更或多或少常见UI的东西很困难。我的应用程序必须具有托盘图标,并能够显示系统通知。

问题在于goroutines本身。在Mac上,对UI框架的任何调用都要求从主线程进行调用,或者至少以线程安全的方式进行调用。

问题出现在当我已经在运行UI时(对于GUI应用程序来说,这是必须的,不是吗?)并尝试显示通知时。原因似乎是systray包的Init函数必须使用runtime.LockOsThread将其锁定到主线程,并且永远不会释放它。然后,如果我尝试显示通知,它也需要runtime.LockOsThread,会导致以下错误:

2016-01-11 22:56:27.973 main[30162:4094392] *** Assertion failure in +[NSUndoManager _endTopLevelGroupings], /Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1256.1/Misc.subproj/NSUndoManager.m:359
2016-01-11 22:56:27.974 main[30162:4094392] +[NSUndoManager(NSInternal) _endTopLevelGroupings] is only safe to invoke on the main thread.
2016-01-11 22:56:27.977 main[30162:4094392] (
    0   CoreFoundation                      0x00007fff8d42bae2 __exceptionPreprocess + 178
    1   libobjc.A.dylib                     0x00007fff8bb03f7e objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff8d42b8ba +[NSException raise:format:arguments:] + 106
    3   Foundation                          0x00007fff8cb4c88c -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 198
    4   Foundation                          0x00007fff8cad24c1 +[NSUndoManager(NSPrivate) _endTopLevelGroupings] + 170
    5   AppKit                              0x00007fff8514206a -[NSApplication run] + 844
    6   main                                0x0000000004166200 nativeLoop + 128
    7   main                                0x0000000004165bca _cgo_8c6479959095_Cfunc_nativeLoop + 26
    8   main                                0x000000000405a590 runtime.asmcgocall + 112
)
2016-01-11 22:56:27.977 main[30162:4094392] *** Assertion failure in +[NSUndoManager _endTopLevelGroupings], /Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1256.1/Misc.subproj/NSUndoManager.m:359
2016-01-11 22:56:27.978 main[30162:4094392] An uncaught exception was raised
2016-01-11 22:56:27.978 main[30162:4094392] +[NSUndoManager(NSInternal) _endTopLevelGroupings] is only safe to invoke on the main thread.

有没有解决方法?到目前为止,我能想到的唯一办法是将UI和通知放入单独的二进制文件中,并通过某种IPC与主文件进行通信。但我可能遗漏了一些东西。

英文:

I am finding it difficult to write something with more or less common UI at least for Mac. My application has to have tray icon and be able to show system notifications

The issue is the goroutines themselves. Any call to UI frameworks on Mac requires that the call is made from main thread, or at least in a thread-safe manner.

The issue arise when I am already running UI (well, for GUI application that is a must, no?) and try to show notification. The reason for this seems to be that systray package Init function has to be locked to main thread using runtime.LockOsThread and never releases it. Then if I try to show notification which also requires runtime.LockOsThread it causes following error:

2016-01-11 22:56:27.973 main[30162:4094392] *** Assertion failure in +[NSUndoManager _endTopLevelGroupings], /Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1256.1/Misc.subproj/NSUndoManager.m:359
2016-01-11 22:56:27.974 main[30162:4094392] +[NSUndoManager(NSInternal) _endTopLevelGroupings] is only safe to invoke on the main thread.
2016-01-11 22:56:27.977 main[30162:4094392] (
    0   CoreFoundation                      0x00007fff8d42bae2 __exceptionPreprocess + 178
    1   libobjc.A.dylib                     0x00007fff8bb03f7e objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff8d42b8ba +[NSException raise:format:arguments:] + 106
    3   Foundation                          0x00007fff8cb4c88c -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 198
    4   Foundation                          0x00007fff8cad24c1 +[NSUndoManager(NSPrivate) _endTopLevelGroupings] + 170
    5   AppKit                              0x00007fff8514206a -[NSApplication run] + 844
    6   main                                0x0000000004166200 nativeLoop + 128
    7   main                                0x0000000004165bca _cgo_8c6479959095_Cfunc_nativeLoop + 26
    8   main                                0x000000000405a590 runtime.asmcgocall + 112
)
2016-01-11 22:56:27.977 main[30162:4094392] *** Assertion failure in +[NSUndoManager _endTopLevelGroupings], /Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1256.1/Misc.subproj/NSUndoManager.m:359
2016-01-11 22:56:27.978 main[30162:4094392] An uncaught exception was raised
2016-01-11 22:56:27.978 main[30162:4094392] +[NSUndoManager(NSInternal) _endTopLevelGroupings] is only safe to invoke on the main thread.

Is there a workaround that? All I could think of so far is to put UI and Notifications into separate binaries and make them to communicate with main over some sort of IPC. But I may be missing something.

答案1

得分: 3

由于这个问题没有足够的关注,我决定发布我自己找到的解决方案,以解决这个问题。由于可能有其他人提供更好的解决方案,所以这个解决方案暂时不会被标记为答案。

我将UI进程中的一部分(即使用系统托盘的部分)移动到另一个二进制文件中,并使用cmd := exec.Command(...)cmd.Start()来调用它,然后我通过管道传输stdinstdout与这个子进程进行通信。

示例代码可以在Github上找到。警告:这个代码片段中有一个错误,即在子进程退出后,主进程将开始消耗CPU周期。请随意自行修复。

我之所以不想使用RPC,是因为这会变得稍微复杂,而且不提供简单的双向通信方式。

英文:

Since there is not enough traction on this question I've decided to post my own solution I found while trying to workaround this issue. This won't be marked as answer yet since someone else may provide better solution.

I have moved one of the UI processes (namely part that uses systray) into another binary and call it using cmd := exec.Command(...) and cmd.Start() then I pipe stdin and stdout and communicate with this child process through those.

The example code can be found on Github. Warning there is an error in this gist where after child exits main process will start burning through CPU cycles. Feel free to fix it yourself.

The reason why I did not want to go through with RPC is because this will become slightly too complex to what I want to achieve and does not provide easy way to do two way communication.

答案2

得分: 2

看起来你正在使用的两个库都正确地使用了runtime.LockOSThread来进行主线程调用的API;不幸的是,要使用多个这样的库,你需要做一些比提供的示例代码更复杂的事情。你需要编写自己的主线程/main.Main调用的消息循环,处理对多个MTO API的调用。

runtime.LockOSThread是与此类API一起操作的解决方案的一部分;golang维基有一个页面介绍了如何使用它与“仅从主线程调用”的API进行交互。

关于程序应该如何更改的极简描述:

你应该在main.init中使用runtime.LockOSThread来确保主线程正在运行main.Mainmain.Main应该重构为两个部分:

  1. 启动一个或多个goroutine来运行以前在main.Main中的内容;
  2. 进入一个消息循环,接收消息并在一个或多个通道上执行某些主线程操作。
英文:

It looks like the two libraries that you are using both correctly use runtime.LockOSThread to make main-thread-only API calls; unfortunately, to use more than one such library, you'll have to do something fancier than the example code that either provides. You'll need to write your own main thread / main.Main-invoked message loop that handles calls to multiple MTO APIs.

runtime.LockOSThread is part of the solution to operating with APIs such as this; the golang wiki has a page about how to use it to interact with "call from main thread only" APIs.

An extremely short description of how your program should change:

You'll want to use runtime.LockOSThread in main.init to make sure that the main thread is running main.Main; main.Main should be refactored into two parts:

  1. starts a goroutine or goroutines that run what previously was in main.Main;
  2. enters a message loop receiving messages to take certain main-thread actions on one or more channels

huangapple
  • 本文由 发表于 2016年1月11日 23:16:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/34724912.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定