英文:
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()
来调用它,然后我通过管道传输stdin
和stdout
与这个子进程进行通信。
示例代码可以在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.Main
;main.Main
应该重构为两个部分:
- 启动一个或多个goroutine来运行以前在
main.Main
中的内容; - 进入一个消息循环,接收消息并在一个或多个通道上执行某些主线程操作。
英文:
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:
- starts a goroutine or goroutines that run what previously was in
main.Main
; - enters a message loop receiving messages to take certain main-thread actions on one or more channels
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论