Async on linux

why can't print out something?

import Foundation

#if canImport(FoundationNetworking)
    import FoundationNetworking
#endif

#if os(Linux)
    import Glibc
#else
    import Darwin
#endif

@propertyWrapper
class Wrapped<T> {
    var wrappedValue: T
    init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }
}

struct VM {
    @MainActor
    @Wrapped
    var val = 0

    @MainActor
    func increaseOne() {
        val += 1
    }

    @MainActor
    func printVal() {
        print(val)
    }
}

let vm = VM()

Task {
    await MainActor.run {
        vm.increaseOne()
        vm.printVal()
    }

}

sleep(5)

The problem is that in the above, you're creating a task to run on the MainActor (aka the main thread) while on the main thread already, and then you use the older form of sleep (instead of Task.sleep), which keeps the main thread occupied. Then the program exits, and the task never gets a chance to run.

The best way to fix this today is to move the code you have at the top level into an async "main" function like this:

@main
struct Main {
  static func main() async {
    let vm = VM()

    let t = Task {
      // since Task.init inherits isolation of the context, no awaits needed.
      // frankly, this task isn't needed at all, but I kept it for your example.
      vm.increaseOne()
      vm.printVal()
    }

    await t.value // much better than trying to sleep; wait for the task to finish.
  }
}

You'll need to compile this file with the flag -parse-as-library for the @main attribute to work correctly.

1 Like

i manage to make it compile.
but something i don't understand.

why do these two lines not request await in your code?

      vm.increaseOne()
      vm.printVal()

because task init is async, isn't?
the earlier code I posted I use MainActor.run, so the closure will run the code on main thread as the method were marked as @MainActor. so, no await to be marked is reasonable. in my code, these two lines in Task's closure without using MainActor.run, I would get error and not able to compile.

what do you mean of Task.init inherits isolation of the context, no awaits needed.?

Task is an async call, isn't it? It doesn't request await. but the closure clearly runs on other thread.

None of the Task initializers are async. Just read the documentation. It'll tell you that.

Task is a struct, not a function, so it's unclear what you mean by this. Please be more specific.

Also, your issue is not specific to Linux. If you run the code in the initial post on macOS, you'll experience the same behavior.

I did read the foundation of task in GitHub. I know it is initialiser. But passing a callback.
I know Task is Task.init, passing a callback to it. But as I read the original code of Task, the call back is passed to an async call.

why do these two lines not request await in your code?

The @main attribute turns the Main.main async function into the entry-point of your program. Because the program always starts on the main thread, the actor-isolation of that function Main.main is implicitly MainActor.

Next, when you call Task.init in a function that has actor isolation, then the closure passed to it is assumed to automatically run on that same actor. Thus, there is no need for await to call those two methods, because the closure is already on the right actor.

You could use Task.detached to create a new task that does not make that assumption about the closure it is passed (that's the primary difference between the detached and regular task creation).

As an example, to explicitly make a closure be isolated to the MainActor, you can annotate the closure manually:

Task.detached { @MainActor () -> Void in print("hi") } 

Task is an async call, isn't it?

Within the Swift language, it is not an async function, so it's not an async call in a precise sense. But more loosely, Task.init does not block the thread it is on to run the closure. Task creation spawns a separate execution context for that closure, schedules it for execution, and then returns. So you could think of it as "asynchronously spawning a task", but I think it's more accurate to say that you're "creating a new asynchronous context". More generally, I would suggest watching our WWDC videos on Swift concurrency to help clarify these concepts and the terminology.

swift/Task.swift at main · apple/swift (github.com)

the parameter in the init method, you could see the operation is async. but anyway, I think because of @_inheritActorContext @_implicitSelfCapture

Right, so Task.init and Task.detached are not async functions, but they accept an async function as an argument for the operation that the task will perform. So creating a task does not require an await.