Task does not run when main defined in a type

Using Swift on Windows 5.6 this code works:

struct Dummy {
}

@main
extension Dummy {
	static func main() {
		_ = Task { print("in task in main") }
		print("in main")
		print("in main")
		print("in main")
		print("in main")
		print("in main")
		print("in main")
	}
}

This code does not run the task:

@main
struct Dummy {
	static func main() {
		_ = Task { print("in task in main") }
		print("in main")
		print("in main")
		print("in main")
		print("in main")
		print("in main")
		print("in main")
	}
}

Is it a bug or my fault?

Answered by otabuzzman in 729701022

Thx for pointing me to the Swift on Windows forum. For now, you gave the answer to my question: anti-pattern. I'll change the design of my app to use structured concurrency as you suggested. That's easy to do and conforms to Swift concurrency.

Doesn't your struct Dummy have to define what it is? For example:

@main
struct Dummy : App {

I have no idea, just a guess from looking at my own code.

It’s weird that you’re seeing different behaviour here. On my Mac (running 12.6) neither prints in task in main, and that’s what I’d expect. You’ve started an unstructured task and told it to run independently of the task running main(). At some point that task will run but, before that happens, you return from main(), which terminates your entire process.

Consider this program:

@main
struct Dummy {
	static func main() async {
		_ = Task { print("in task in main") }
		print("in main 1")
		print("in main 2")
		print("in main 3")
		print("in main 4")
		print("in main 5")
        try! await Task.sleep(nanoseconds: 1_000)
		print("in main 6")
	}
}

It prints

in main 1
in main 2
in main 3
in main 4
in main 5
in task in main
in main 6

because the sleep gives the program enough time to spin up the task.

The best way to avoid this problem is to use structured concurrency. That guarantees that every task started in a scope has completed before that scope does away. So, something like this:

func task() async -> Int {
    print("in task in main")
    return 42
}

@main
struct Dummy {
	static func main() async {
		async let result = task()
		print("in main 1")
		print("in main 2")
		print("in main 3")
		print("in main 4")
		print("in main 5")
        try! await Task.sleep(nanoseconds: 1_000)
		print("in main 6")
        print(await result)
	}
}

Alternatively, you can continue using unstructured concurrency and explicitly wait for your task:

@main
struct Dummy {
	static func main() async {
		let t = Task { print("in task in main") }
		print("in main 1")
		print("in main 2")
		print("in main 3")
		print("in main 4")
		print("in main 5")
        print(await t.result)
		print("in main 6")
	}
}

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Your reply makes perfect sense, but (as there is always a but) the six print statements in my example were actually supposed to give the task time to complete (and on my Winos box they do). If I substitute them with an infinite loop the behavior is the same: the task execs with main defined in extension Dummy and does not exec with main in struct Dummy. That infinite loop thing is quite close to the logic of my app which first starts an independent background task to create files and then loops forever processing these files as they occur.

Task execs:

struct Dummy {}

@main
extension Dummy {
	static func main() {
		_ = Task { print("in task in main") }
		
		var c = 0
		while true {
			c += 1
		}
	}
}

Task not exec'ing:

@main
struct Dummy {
	static func main() {
		_ = Task { print("in task in main") }
		
		var c = 0
		while true {
			c += 1
		}
	}
}

Inspired by your examples I found adding a rather short Task.sleep makes my task work with main defined in struct Dummy:

@main
struct Dummy {
	static func main() async {
		_ = Task { print("in task in main") }
		
		try? await Task.sleep(nanoseconds: 1)
		
		var c = 0
		while true {
			c += 1
		}
	}
}

It's somewhat academic but I really would like to know, well why...

Unfortunately there are limits to how much I can help you here. I’m not familiar with how Swift concurrency is implemented on Windows.

What I can say is that this is an anti-pattern:

@main
struct Dummy {
	static func main() {
		_ = Task { print("in task in main") }
		
		var c = 0
		while true {
			c += 1
		}
	}
}

Depending on how Swift concurrency is implemented, that while loop might be tying down one of the Swift concurrency worker threads. If Swift concurrency runtime has limited worker threads [1], that could prevent your task from making progress.

If you need more insight on the Windows side, I recommend that you bounce over to Swift Forums > Using Swift. There are folks there that use Windows every day (whereas I use it ever decade or so!).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] You see this in the iOS Simulator. The Swift concurrency runtime on iOS has a worker thread per core but the simulator has just a single one (at least the last time I looked).

Accepted Answer

Thx for pointing me to the Swift on Windows forum. For now, you gave the answer to my question: anti-pattern. I'll change the design of my app to use structured concurrency as you suggested. That's easy to do and conforms to Swift concurrency.

Task does not run when main defined in a type
 
 
Q