Cocoa:如何在执行后台任务时运行模式窗口?

发布于 2024-12-01 21:45:18 字数 281 浏览 0 评论 0 原文

我尝试调用

modalSession=[NSApp beginModalSessionForWindow:conversionWindow];
[NSApp runModalForWindow:conversionWindow];

以获得模式转换窗口,以防止用户与应用程序的其余部分交互,但这似乎也会阻止代码执行。我的意思是上面显示的代码后面的代码根本没有执行。我该如何解决这个问题?我确信这是可能的,因为许多应用程序在执行一些大任务(例如视频转换等)时显示出一些进展......

I've tried calling

modalSession=[NSApp beginModalSessionForWindow:conversionWindow];
[NSApp runModalForWindow:conversionWindow];

in order to get a modal conversionWindow that prevents the user to interact with the rest of the application, but this also seems to block code execution. What I mean is that the code that comes after the code shown above isn't executed at all. How can I fix this? I'm sure this is possible because many applications show some progress while performing some big task, like video conversion etc...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

眼前雾蒙蒙 2024-12-08 21:45:18

除非绝对必要,否则请不要使用应用程序模式窗口。如果可能的话,使用一张纸。但是,如果必须使用模式对话框,则可以在模式对话框打开时给主运行循环一些时间来运行主运行循环:

NSModalSession session = [NSApp beginModalSessionForWindow:[self window]];
int result = NSRunContinuesResponse;

while (result == NSRunContinuesResponse)
{
    //run the modal session
    //once the modal window finishes, it will return a different result and break out of the loop
    result = [NSApp runModalSession:session];

    //this gives the main run loop some time so your other code processes
    [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode];

    //do some other non-intensive task if necessary
}

[NSApp endModalSession:session];

如果您有需要主运行循环才能运行的视图,这非常有用(我想到了 WebView)。

但是,请理解模态会话就是这样,调用 beginModalSessionForWindow: 之后的任何代码都不会执行,直到模态窗口关闭且模态会话结束。这是不使用模式对话框的一个很好的理由。

请注意,您不得在上面代码的 while 循环中执行任何重要工作,因为这样您将阻塞模态会话以及主运行循环,这会将您的应用程序变成沙滩球城市。

如果你想在后台做一些实质性的事情,你必须使用某种形式的并发,例如使用 NSOperation、GCD 后台队列或只是一个普通的后台线程。

Please don't use an app-modal window unless it's absolutely necessary. Use a sheet if possible. However, if you must use a modal dialog, you can make the main run loop run by giving it some time while the modal dialog is open:

NSModalSession session = [NSApp beginModalSessionForWindow:[self window]];
int result = NSRunContinuesResponse;

while (result == NSRunContinuesResponse)
{
    //run the modal session
    //once the modal window finishes, it will return a different result and break out of the loop
    result = [NSApp runModalSession:session];

    //this gives the main run loop some time so your other code processes
    [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode];

    //do some other non-intensive task if necessary
}

[NSApp endModalSession:session];

This is very useful if you have views that require the main run loop to operate (WebView comes to mind).

However, understand that a modal session is just that, and any code after the call to beginModalSessionForWindow: will not be executed until the modal window closes and the modal session ends. This is one very good reason not to use modal dialogs.

Note that you must not do any significant work in the while loop in the code above, because then you will block your modal session as well as the main run loop, which will turn your app into beachball city.

If you want to do something substantial in the background you must use some form of concurrency, such as a using NSOperation, a GCD background queue or just a plain background thread.

终遇你 2024-12-08 21:45:18

如果您希望后台“任务”在模态窗口启动时运行,则需要在另一个线程中启动后台“任务”。但在大多数情况下,最好的解决方案是不使用模式窗口。

You need to start the background "task" in another thread if you want it to run while a modal window is up. But in most cases, the best solution is to not use a modal window.

折戟 2024-12-08 21:45:18

这就是模态窗口的工作原理:窗口同步呈现,并且在模态窗口会话停止后执行NSApp.runModal(for: window)之后的代码。

在幕后,模态窗口在特殊的模态事件循环中运行,因此仅处理指定窗口中发生的事件。

在代码中,它确实在 NSApp.runModal(for: window) 处停止执行,但您仍然可以从窗口分派作业。例如,模态窗口上触发下载任务的按钮。

您可以使用:

  1. performSelector(onMainThread:with:waitUntilDone:modes:) 派遣工作。
  2. GCD 派遣工作。

您认为模态窗口阻止代码执行的一种常见情况可能是:

// present a modal window in GCD main queue asynchronously:
DispatchQueue.main.async {
  NSApp.runModal(for: window)
  // only executes after modal window is dismissed
}

并且在其他地方尝试使用 GCD 进行分派。例如:

// modal window's button action handler:
button.actionHandler = {
  DispatchQueue.main.async {
    // some button action...
  }
}

由于 NSApp.runModal(for: window) 不会离开已分派的块,因此在模态窗口被关闭之前,GCD 主队列不会执行后续块。

为了避免此阻塞问题,您可以:

  1. 避免在 DispatchQueue.main 中调用 NSApp.runModal(for: window)
  2. 使用 performSelector(onMainThread:with:waitUntilDone:modes:) 进行调度一份工作。

对于第二个解决方案,我为自己做了一个方便的助手:

public func onMainThread(waitUntilDone: Bool = false, block: @escaping BlockVoid) {
  _ = MainRunloopDispatcher(waitUntilDone: waitUntilDone, block: block)
}

private final class MainRunloopDispatcher: NSObject {

  private let block: BlockVoid

  init(waitUntilDone: Bool = false, block: @escaping BlockVoid) {
    self.block = block

    super.init()

    performSelector(onMainThread: #selector(execute), with: nil, waitUntilDone: waitUntilDone)
  }

  @objc private func execute() {
    block()
  }
}

这样当GCD主队列被阻塞时,您可以使用在主线程上调用一些代码

onMainThread {
  // here is on main thread, update UI...
}

来阅读更多内容,这个文章帮助了我。

That is how a modal window works: the window is presented synchronously, and the code after NSApp.runModal(for: window) is executed after the modal window session is stopped.

Under the hood, the modal window runs in a special modal event loop, so that only the events happen in the specified window are processed.

In code, it does stop the execution at the point of NSApp.runModal(for: window), but you can still dispatch jobs from the window. For example, a button on the modal window that triggers a download task.

You can use:

  1. performSelector(onMainThread:with:waitUntilDone:modes:) to dispatch a job.
  2. GCD to dispatch a job.

One common scenario that you feel the modal window blocks code execution can be:

// present a modal window in GCD main queue asynchronously:
DispatchQueue.main.async {
  NSApp.runModal(for: window)
  // only executes after modal window is dismissed
}

and somewhere else tries to dispatch using GCD. For example:

// modal window's button action handler:
button.actionHandler = {
  DispatchQueue.main.async {
    // some button action...
  }
}

Because NSApp.runModal(for: window) doesn't leave the dispatched block, the GCD main queue won't execute following blocks until the modal window is dismissed.

To avoid this blocking issue, you could:

  1. Avoid calling NSApp.runModal(for: window) in DispatchQueue.main.
  2. Use performSelector(onMainThread:with:waitUntilDone:modes:) to dispatch a job.

For the 2nd solution, I made a convenient helper for myself:

public func onMainThread(waitUntilDone: Bool = false, block: @escaping BlockVoid) {
  _ = MainRunloopDispatcher(waitUntilDone: waitUntilDone, block: block)
}

private final class MainRunloopDispatcher: NSObject {

  private let block: BlockVoid

  init(waitUntilDone: Bool = false, block: @escaping BlockVoid) {
    self.block = block

    super.init()

    performSelector(onMainThread: #selector(execute), with: nil, waitUntilDone: waitUntilDone)
  }

  @objc private func execute() {
    block()
  }
}

So that when the GCD main queue is blocked, you can use to call some code on the main thread

onMainThread {
  // here is on main thread, update UI...
}

To read more, this article helped me.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文