I am making a macOS app. I have a task (a script) that I am running with Process() in Swift 3. When I press a button (button.State == NSOnState), I would like the task to repeating n times, and terminate earlier until the button is pressed again (button.State == NSOffState).
I looked up how to repeat a task, and it looks possible with a simple for loop – for i in {1..n}.
Now, the problem I am having is that it doesn't seem to be possible to call a task multiple times. When I try to call the task the second time, I get an error in the console:
[General] task already launched
Here is my code:
#IBAction func buttonPressed(_ sender: Any) {
let script = "for i in {1..5}; do echo \"hi\"; done; sleep 1"
let task = Process()
task.terminationHandler = self.commandTerminationHandler
task.launchPath = "/bin/bash"
task.arguments = ["-c", script]
if button.state == NSOnState {
task.launch() // launches task
task.waitUntilExit() // waits until task has been completed (about 1 second)
task.terminate() // (should) terminate the task. (The console error occurs with or without this line)
task.launch() // tries launching the task again, but this results in the console error.
print("The task was launched twice")
} else {
// task.terminate()
}
}
I googled this error, and found [this][https://forums.macrumors.com/threads/nstask-not-terminating.1617855/#post-17677541]:
The error isn't that the task is still running. It's that the task has already run and completed, and can't be started again. You'll need to create a new NSTask object to run the task again.
So I need to make a new NSTask (or Process as of Swift 3) and keep making new ones to repeat the code forever. This sound very complicated (as if I'm using a workaround) and is probably inefficient.
Is there a better way to repeat a Process in Swift 3?
For the sake of completeness, I'd also like to mention that I considered using for i in {1..n} do ... done directly in script. This has one problem:
It doesn't look like it's possible to stop the task when the button is pressed again. This is because if I run task.terminate(), I get the error "task not launched." The only way I can stop it is by running killall bash in my Terminal, which doesn't seem like a nice solution. To do this in Xcode, I'd need make a Process to kill bash with bash... which is strange.
I'm having the same issue. A potential fix that I've come across online is to set the task to nil after terminating it, then reassigning parameters such as path, arguments, etc before running it again. However this can only be done in Objective C; Swift has very strict rules when it comes to nil (and probably for good reasons).
You can run 2 different script at the same time. One of them you want to run should be running and the other always check the another script at every turn and this should be like recursive. The another script will kill the another script until the button is pressed.
If you set your task item to a var instead of a let you can repeatedly set task to a new process object inside your loop.
var task = Process()
task.launchPath = ....
task.arguments = [...]
task = Process()
task.launchPath = ....
task.arguements = [...]
Related
I'm in a project in which I have to run an Anylogic simulator multiple times. I made an script to modify the input data, run the simulator and then store the output data just before the next simulation run.
The idea is to externally run the simulation (from a python file). The problem is that, when the simulation ends, the simulation window doesn't close automatically so the python file won't continue executing.
I´ve tried to run the simulator without showing the animation of the simulation but still opens a window so it doesn´t work for my purpose.
I don´t know if there is an option in Anylogic to export a model that automatically closes the window once the simulation is completed or if there is any way of creating a simulator that runs without opening any window.
Thank you.
Unfortunately there is no such solution. Even if you can run without UI in Linux, it will not automatically close once the run is complete. I use a workaround:
It is a Python script that scans the outputs folder every 5 seconds and if there are changes in the files, it closes the AnyLogic file. Use this as an inspiration::
from time import sleep
from utils.data.fileSystem import FileSystem
def sync_polling_folder(path, predicate, delay_sec):
print('checking under ' + path + ' folder')
beginning = FileSystem.stat(path)
old = beginning
new = beginning
def two_files_are_different():
return not predicate(str(old), str(new))
def the_process_has_not_begun():
return str(new) == str(beginning) or str(old) == str(beginning)
# if two folders are the same, quit (means no-changes = finished)
# but if they are equal because process never started, keep going
while two_files_are_different() or the_process_has_not_begun():
print('[sleeping] because files are not written yet.')
sleep(delay_sec) # main thread waiting
old = new
new = FileSystem.stat(path)
print('[anylogic] ready to be killed')
return True
I am developing an app for monitoring the highest consuming processes in Swift, but I'm stuck at the part of obtaining the list of processes that are currently running. I've tried a lot of things, such as:
Running the top or ps aux | less commands and parsing the output.
I tried using this code to run the top command and pass the output to a NSPipe in order to parse it later, but I can't seem to run the command because it gives the error Couldn't posix_spawn: error 13, and I couldn't find anything on the internet on how to fix this, so I had to find another way.
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.launchPath = "/usr/bin"
task.arguments = ["top"]
task.launch()
task.waitUntilExit()
let data = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
Using NSWorkspace.shared.runningApplications
I saw this stack overflow question regarding the same topic, but it isn't answered (one comment references another thread that answers how to do it in C, but it isn't what I actually expected). The thread's OP used the code below in order to get the full list of running processes, but it only returns the user-owned ones, so it isn't really useful.
let workspace = NSWorkspace.shared
let applications = workspace.runningApplications
for application in applications {
if let url = (application.executableURL?.absoluteString) {
os_log("%{public}s", log:scribe, type:.debug, url)
}
}
}
Conclusion
Is there a way I can get a list of running processes in macOS (including those owned by root) in Swift? If there's another way through which I could retrieve at least the two most CPU-consuming processes that would do as well.
Thanks in advance.
I have a process running in a DispatchQueue which creates a temporary file. The file is deleted in a defer block so the clean up occurs regardless of whether an error is thrown or I just return from process() normally. See code below
func process() throws {
let file = createTemporaryFile()
defer {
deleteTemporaryFile(file)
}
try callCodeThatMightThrowErrors()
}
dispatchQueue.async {
do {
try process()
} catch {
dealWithError()
}
}
Now this all works fine until I quit my application. If I have a DispatchQueue currently in the middle of the process() function the defer block is not run and the file is not deleted and I leave a temporary file in the system. Is there any way I can get this defer block to be called? I would rather not have to store a global array of temporary files that need to be deleted at application exit.
You need to either:
a) prevent your app from terminating while your process is running, OR
b) know when termination is happening and cancel your process
Either way, NSApplicationDelegate has a method (applicationShouldTerminate) to ask you if it can terminate. While your process is running, you should return NSTerminateLater, and then when the process is done, call replyToApplicationShouldTerminate.
You should also make sure that sudden termination is disabled while your process is running so that you actually get the termination delgation. See ProcessInfo disableSuddenTermination`
Do you really have to clean up on app exit though? If you are sure that no temporary file can exist on app start then add your cleanup code there. That way no matter how a user or the OS terminated the app, your cleanup code would run. Of course, if the process is not terminated you can clean the temp files where you do it now.
I am trying to run a commandline tool using swift 2 on a mac (10.10):
let task = NSTask()
task.launchPath = "/path/to/wrong/binary"
task.launch()
// NSPipe() stuff to catch output
task.waitUntilExit()
// never reached
if (task.terminationStatus != 0){
NSLog("uh oh")
}
Since the path is wrong, my program dies with launch path not accessible. However, I don't know, how to catch this error. Using do { try } catch {} around task.launch() does not work, because it does not throw an exception, looking at the terminationStatus is also not working, since it is never reached.
How can I catch a wrong launchPath?
Apple Swift version 2.1.1 (swiftlang-700.1.101.15 clang-700.1.81)
Target: x86_64-apple-darwin14.5.0
In Mac OS X High Sierra, launch() is deprecated.
You would use run() instead:
let process = Process()
// ...
do {
process.run()
} catch let error as NSError {
print(error.localizedDescription)
// ...
}
Also have a look at the Apple Dev Docs
unfortunately, there is no chance to catch runtime exceptions. with try / catch you can recovery from trowing error, not from runtime exception. you can create you own exception handler, but you are still not able to recover from it. try to lunch from NSTask some common shell with command as a parameter and next use a pipe to return os errors to you own code.
import Foundation
let task = Process()
let pipe = Pipe()
task.launchPath = "/bin/bash"
task.arguments = ["-c","unknown"]
task.standardOutput = pipe
task.launch()
let handle = pipe.fileHandleForReading
let data = handle.readDataToEndOfFile()
let dataString = String(data: data, encoding: .utf8)
print(dataString ?? "")
will print
/bin/bash: unknown: command not found
You're not supposed to catch it. Your computer, the local environment that you app is running in, is deemed as a reliable medium. Therefore, NSTask is designed to expect that any path you feed it is guaranteed to exist. This holds up fine as folks are usually using NSTask to launch standard system executables (bash, diff, rsync, etc...). The crash on an mistyped path is meant to help you find bugs in your code easily. If you're launching your own custom-built binaries embedded in the app's bundle, you should really be using XPC instead.
If you absolutely need to use NSTask to launch unreliable paths, before you create your object, you need to validate the path manually using NSFileManager's fileExistsAtPath(_:isDirectory:) and react accordingly. If even the POSIX permissions flags are unreliable (at which point, your app likely has some serious security problems; if this is a consumer application project, I recommend you rethink your design), you'll also want to check the file's NSFilePosixPermissions attribute to make sure it's executable.
I'm using Task Scheduler Managed Wrapper from Codeplex and I need to fire task as soon as possible and only once. I don't found any API that could execute created task immediately (I can be wrong). So how can I create a trigger that fires only once and as soon as possible?
Found solution here.
using (TaskService ts = new TaskService())
{
string task = "TaskName";
// This will find it even if its down in a folder. Alternately you could call:
// Task t = ts.GetTask(task);
Task t = ts.FindTask(task);
if (t != null)
t.Run();
}