Kotlin协程等效的方式,就像Spring Java中的@Async

huangapple go评论62阅读模式
英文:

Kotlin Coroutine equivalent way as @Async in Spring Java

问题

Spring提供了使用@Async@EnableAsync注解来支持异步执行的方式。在下面的Java代码中,使用这些注解后,我的API不会被阻塞,并且可以在昂贵的操作完成之前响应。

suspend fun saveUserDetail(userDetail: UserDetail): Response {
    println("saveUserDetail Started...")
    // very expensive operation
    userRepo.save(userDetail)
    println("saveUserDetail Completed...")
    return response // replace 'response' with the actual response object
}

suspend fun save(userDetail: UserDetail) {
    // took 30s
    delay(30000)
}

请注意,上述Kotlin代码使用了suspend关键字来标记可以挂起的函数,同时使用了delay函数来模拟异步操作的等待时间。

英文:

Spring provides a way to support asynchronous execution with annotation of @Async and @EnableAsync, in the below code as Java, my API won't be blocked and can respond without waiting for the expensive operation to be finished.

@Post
public Response saveUserDetail(UserDetail userDetail) {
   System.out.println("saveUserDetail Started...");
   // very expensive operation
   userRepo.save(userDetail);
   System.out.println("saveUserDetail Completed...");
}

@Async
public void save(UserDetail userDetail) {
   // took 30s
   Thread.sleep(30000);
}

Now Kotlin has a coroutine package to write an async process in a sync way, however, what'd be the Kotlin way to achieve the above behaviour without using @Async? I've tried suspend and async but they seem will still block my request thread that I cannot get my API response without waiting in expensive operation

答案1

得分: 1

Kotlin协程与常规Java线程不同。协程具有两个与线程不同的特性,我认为与您相关:

  1. 协程使用结构化并发。外部协程范围不会完成,直到所有子协程完成为止。

  2. 协程不应该被诸如Thread.sleep()等调用阻塞,除非采取谨慎的措施在专为此设计的调度程序中运行此类阻塞代码,例如Dispatchers.IO。而是使用类似delay()的挂起调用。有关详细信息,请参阅https://stackoverflow.com/a/76009163/430128。

现在在您的情况下,似乎您希望saveUserDetail在不等待save完成的情况下完成。请记住,这段Spring代码不安全,因为如果saveUserDetail抛出错误,客户端将永远不会知道。或者如果由于某种原因saveUserDetail永远不返回,线程将泄漏。 Kotlin中的结构化并发旨在通过默认方式防止这种不安全的代码。使用协程和结构化并发,响应将不会在saveUserDetail调用完成之前返回,而且无论是否使用asynclaunch协程构建器,都没有关系,因为默认情况下它们只是启动一个子协程。父协程仍然不会在由asynclaunch启动的子协程完成之前完成。

如果您真的想要这段不安全的代码版本,您需要将协程启动到不是请求范围子协程的不同协程范围中,例如GlobalScope。例如:

GlobalScope.launch {
  userRepo.save(userDetail);
}

确保阅读文档中使用GlobalScope的所有注意事项。

您还可以创建自己的后台范围并使用它。示例:

val backgroundOpsScope = CoroutineScope(SupervisorJob())
...
suspend fun saveUserDetail(userDetail: UserDetail) {
  backgroundOpsScope.launch {
    ...昂贵的操作...
  }
}
英文:

Kotlin coroutines are not the same as regular Java threads. Coroutines have two properties that are different than threads which I believe are relevant to you:

  1. Coroutines use structured concurrency. An outer coroutine scope will not complete until all child coroutines are complete.

  2. Coroutines should not be blocked by calls such as Thread.sleep() unless care is taken to run such blocking code in a dispatcher designed for it, such as Dispatchers.IO. Instead, use a suspending call like delay() See https://stackoverflow.com/a/76009163/430128 for details about this.

Now in your case, it appears you want saveUserDetail to complete without waiting for save to finish. Keep in mind this Spring code isn't safe, because if saveUserDetail throws an error, the client will never know about it. Or if saveUserDetail never returns for some reason, the thread will leak. Structured concurrency in Kotlin is intended to prevent such unsafe code by default. With coroutines and structured concurrency, the response will not return until the saveUserDetail call finishes, and it doesn't matter if the async or launch coroutine builders are used or not, because by default they simply start a child coroutine. The parent still won't complete until the child coroutine started by async or launch is done.

If you really want the unsafe version of this code, you need to launch the coroutine into a different coroutine scope that is not a child of the request scope, such as the GlobalScope. For example:

GlobalScope.launch {
  userRepo.save(userDetail);
}

Make sure you read all the caveats of using GlobalScope in the docs.

You can also create your own background scope and use that. Example:

val backgroundOpsScope = CoroutineScope(SupervisorJob())
...
suspend fun saveUserDetail(userDetail: UserDetail) {
  backgroundOpsScope.launch {
    ... expensive operation ...
  }
}

huangapple
  • 本文由 发表于 2023年4月20日 09:53:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/76059982.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定