Trong kotlinx.coroutines
thư viện, bạn có thể bắt đầu coroutine mới bằng cách sử dụng launch
(với join
) hoặc async
(với await
). sự khác biệt giữa chúng là gì?
Trong kotlinx.coroutines
thư viện, bạn có thể bắt đầu coroutine mới bằng cách sử dụng launch
(với join
) hoặc async
(với await
). sự khác biệt giữa chúng là gì?
Câu trả lời:
launch
được sử dụng để bắn và quên coroutine . Nó giống như bắt đầu một chủ đề mới. Nếu mã bên trong launch
chấm dứt với ngoại lệ, sau đó nó được coi như còn tự do ngoại lệ trong một thread - thường được in để stderr trong backend ứng dụng JVM và treo các ứng dụng Android. join
được sử dụng để chờ hoàn thành coroutine được phóng và nó không tuyên truyền ngoại lệ của nó. Tuy nhiên, một đứa trẻ bị tai nạn coroutine cũng hủy bỏ cha mẹ của nó với ngoại lệ tương ứng.
async
được sử dụng để bắt đầu một coroutine tính toán một số kết quả . Kết quả được đại diện bởi một thể hiện Deferred
và bạn phải sử dụng await
nó. Một ngoại lệ chưa async
được lưu trong mã được lưu trữ bên trong kết quả Deferred
và không được gửi ở bất kỳ nơi nào khác, nó sẽ bị hủy bỏ âm thầm trừ khi được xử lý. Bạn KHÔNG được quên về coroutine bạn đã bắt đầu với async .
Tôi thấy hướng dẫn này https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md sẽ hữu ích. Tôi sẽ trích dẫn những phần thiết yếu
🦄 coroutine
Về cơ bản, coroutines là chủ đề trọng lượng nhẹ.
Vì vậy, bạn có thể nghĩ về coroutine như một cái gì đó quản lý luồng theo cách rất hiệu quả.
Ra mắt
fun main(args: Array<String>) {
launch { // launch new coroutine in background and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello,") // main thread continues while coroutine is delayed
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}
Vì vậy, launch
bắt đầu một chủ đề nền, làm một cái gì đó và trả lại mã thông báo ngay lập tức Job
. Bạn có thể gọi join
nó Job
để chặn cho đến khi launch
chủ đề này hoàn thành
fun main(args: Array<String>) = runBlocking<Unit> {
val job = launch { // launch new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // wait until child coroutine completes
}
🦆 async
Về mặt khái niệm, async giống như khởi chạy. Nó bắt đầu một coroutine riêng biệt, đó là một sợi có trọng lượng nhẹ, hoạt động đồng thời với tất cả các coroutine khác. Sự khác biệt là việc khởi chạy trả về một Công việc và không mang bất kỳ giá trị kết quả nào, trong khi async trả về Trì hoãn - một tương lai không chặn trọng lượng nhẹ thể hiện lời hứa sẽ cung cấp kết quả sau này.
Vì vậy, async
bắt đầu một chủ đề nền, làm một cái gì đó và trả lại mã thông báo ngay lập tức Deferred
.
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
Bạn có thể sử dụng .await () trên giá trị hoãn lại để nhận kết quả cuối cùng của nó, nhưng Trì hoãn cũng là một Công việc, vì vậy bạn có thể hủy nó nếu cần.
Vì vậy, Deferred
thực sự là một Job
. Xem https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html
interface Deferred<out T> : Job (source)
🦋 async là háo hức theo mặc định
Có một tùy chọn lười biếng để không đồng bộ bằng cách sử dụng tham số bắt đầu tùy chọn với giá trị CoroutineStart.LAZY. Nó chỉ bắt đầu coroutine khi cần một số kết quả hoặc nếu một chức năng bắt đầu được gọi.
launch
và async
được sử dụng để bắt đầu các coroutines mới. Nhưng, họ thực hiện chúng theo cách khác nhau.
Tôi muốn đưa ra ví dụ rất cơ bản sẽ giúp bạn hiểu sự khác biệt rất dễ dàng
- phóng
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnCount.setOnClickListener {
pgBar.visibility = View.VISIBLE
CoroutineScope(Dispatchers.Main).launch {
val currentMillis = System.currentTimeMillis()
val retVal1 = downloadTask1()
val retVal2 = downloadTask2()
val retVal3 = downloadTask3()
Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1}, ${retVal2}, ${retVal3} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
pgBar.visibility = View.GONE
}
}
// Task 1 will take 5 seconds to complete download
private suspend fun downloadTask1() : String {
kotlinx.coroutines.delay(5000);
return "Complete";
}
// Task 1 will take 8 seconds to complete download
private suspend fun downloadTask2() : Int {
kotlinx.coroutines.delay(8000);
return 100;
}
// Task 1 will take 5 seconds to complete download
private suspend fun downloadTask3() : Float {
kotlinx.coroutines.delay(5000);
return 4.0f;
}
}
Trong ví dụ này, mã của tôi đang tải xuống 3 dữ liệu khi nhấp vào btnCount
nút và hiển thị pgBar
thanh tiến trình cho đến khi tất cả quá trình tải xuống hoàn tất. Có 3 suspend
chức năng downloadTask1()
, downloadTask2()
và downloadTask3()
tải dữ liệu. Để mô phỏng nó, tôi đã sử dụng delay()
các chức năng này. Các chức năng này chờ đợi 5 seconds
, 8 seconds
và 5 seconds
tương ứng.
Vì chúng ta đã sử dụng launch
để bắt đầu các hàm tạm ngưng này, launch
sẽ thực hiện chúng một cách tuần tự (từng cái một) . Điều này có nghĩa là, downloadTask2()
sẽ bắt đầu sau khi downloadTask1()
được hoàn thành và downloadTask3()
sẽ chỉ bắt đầu sau khi downloadTask2()
hoàn thành.
Như trong ảnh chụp màn hình đầu ra Toast
, tổng thời gian thực hiện để hoàn thành cả 3 lần tải xuống sẽ dẫn đến 5 giây + 8 giây + 5 giây = 18 giây vớilaunch
- không đồng bộ
Như chúng ta đã thấy điều đó launch
thực hiện sequentially
cho cả 3 nhiệm vụ. Thời gian để hoàn thành tất cả các nhiệm vụ là 18 seconds
.
Nếu các tác vụ đó độc lập và nếu chúng không cần kết quả tính toán của tác vụ khác, chúng tôi có thể làm cho chúng chạy concurrently
. Họ sẽ bắt đầu cùng một lúc và chạy đồng thời trong nền. Điều này có thể được thực hiện với async
.
async
trả về một thể hiện của Deffered<T>
kiểu, trong đó T
loại dữ liệu mà hàm treo của chúng ta trả về. Ví dụ,
downloadTask1()
sẽ trả về Deferred<String>
vì String là kiểu trả về của hàmdownloadTask2()
sẽ trả về Deferred<Int>
vì Int là kiểu trả về của hàmdownloadTask3()
sẽ trả về Deferred<Float>
vì Float là kiểu trả về của hàmChúng ta có thể sử dụng đối tượng trả về từ async
loại Deferred<T>
để lấy giá trị trả về trong T
loại. Điều đó có thể được thực hiện với await()
cuộc gọi. Kiểm tra mã dưới đây chẳng hạn
btnCount.setOnClickListener {
pgBar.visibility = View.VISIBLE
CoroutineScope(Dispatchers.Main).launch {
val currentMillis = System.currentTimeMillis()
val retVal1 = async(Dispatchers.IO) { downloadTask1() }
val retVal2 = async(Dispatchers.IO) { downloadTask2() }
val retVal3 = async(Dispatchers.IO) { downloadTask3() }
Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1.await()}, ${retVal2.await()}, ${retVal3.await()} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
pgBar.visibility = View.GONE
}
Bằng cách này, chúng tôi đã đưa ra cả 3 nhiệm vụ đồng thời. Vì vậy, tổng thời gian thực hiện của tôi để hoàn thành sẽ chỉ 8 seconds
là thời gian downloadTask2()
vì nó là lớn nhất trong cả 3 nhiệm vụ. Bạn có thể thấy điều này trong ảnh chụp màn hình sau trongToast message
launch
là để tuần tự funs, trong khi async
cho đồng thời
launch
và async
sẽ bắt đầu các coroutines mới. Bạn đang so sánh một coroutine không có con với một coroutine với 3 con. Bạn có thể thay thế từng async
yêu cầu launch
và hoàn toàn không có gì thay đổi liên quan đến đồng thời.
cả hai trình xây dựng coroutine cụ thể là launch và async về cơ bản là lambdas với bộ thu kiểu CoroutineScope, có nghĩa là khối bên trong của chúng được biên dịch dưới dạng hàm treo, do đó cả hai đều chạy trong chế độ không đồng bộ VÀ cả hai sẽ thực hiện tuần tự khối của chúng.
Sự khác biệt giữa khởi chạy và không đồng bộ là chúng cho phép hai khả năng khác nhau. Trình xây dựng khởi chạy trả về một Công việc tuy nhiên chức năng async sẽ trả về một đối tượng Trì hoãn. Bạn có thể sử dụng launch để thực thi một khối mà bạn không mong đợi bất kỳ giá trị nào được trả về từ nó, tức là ghi vào cơ sở dữ liệu hoặc lưu tệp hoặc xử lý một cái gì đó về cơ bản chỉ được gọi là tác dụng phụ của nó. Mặt khác, async trả về Trì hoãn như tôi đã nói trước đó trả về một giá trị hữu ích từ việc thực thi khối của nó, một đối tượng bao bọc dữ liệu của bạn, do đó bạn có thể sử dụng nó cho kết quả chủ yếu nhưng cũng có thể cho tác dụng phụ của nó. Lưu ý: bạn có thể loại bỏ phần hoãn lại và nhận giá trị của nó bằng cách sử dụng hàm chờ, điều này sẽ chặn việc thực thi các câu lệnh của bạn cho đến khi một giá trị được trả về hoặc một ngoại lệ được đưa ra!
cả trình xây dựng coroutine (khởi chạy và không đồng bộ) đều có thể hủy được.
Còn gì nữa không ?: vâng với khởi chạy nếu một ngoại lệ được ném trong khối của nó, coroutine sẽ tự động bị hủy và các ngoại lệ được gửi. Mặt khác, nếu điều đó xảy ra với async, ngoại lệ sẽ không được truyền bá thêm nữa và nên được bắt / xử lý trong đối tượng Trì hoãn được trả lại.
thêm về coroutines https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1
khởi động trả lại một công việc
async trả về một kết quả (công việc hoãn lại)
khởi chạy với phép nối được sử dụng để đợi cho đến khi công việc kết thúc. Nó chỉ đơn giản là tạm dừng lệnh gọi coroutine (), để luồng hiện tại tự do thực hiện các công việc khác (như thực hiện một coroutine khác) trong thời gian đó.
async được sử dụng để tính toán một số kết quả. Nó tạo ra một coroutine và trả về kết quả trong tương lai của nó như là một triển khai của Trì hoãn. Coroutine đang chạy bị hủy khi kết quả hoãn lại bị hủy.
Hãy xem xét một phương thức async trả về giá trị chuỗi. Nếu phương thức async được sử dụng mà không chờ đợi, nó sẽ trả về một chuỗi Trì hoãn nhưng nếu await được sử dụng, bạn sẽ nhận được một chuỗi như kết quả
Sự khác biệt chính giữa async và khởi chạy. Trì hoãn trả về một giá trị cụ thể của loại T sau khi Coroutine của bạn kết thúc thực thi, trong khi Công việc thì không.
Async vs Launch Async vs Launch Diff Image
khởi chạy / không đồng bộ
không đồng bộ cho kết quả