Seri Konkurensi Swift 03|Cara memahami Tugas, Tugas.detached, dan MainActor
Mereka sering muncul bersama-sama, namun mereka menjawab tiga pertanyaan masing-masing: "Dari mana tugas itu berasal, siapa yang terikat padanya, dan siapa yang mengubah UI?"
Ketika saya pertama kali mengenal Swift Concurrency, saya bingung tentang Task, Task.detached dan MainActor:
- Semua terkait dengan asynchronous
- sering muncul di bagian kode yang sama
- Semuanya terlihat seperti “biarkan kode berjalan di suatu tempat”
Jadi cara paling umum untuk belajar adalah dengan menghafal definisi. Namun, jika Anda hanya mengandalkan definisi untuk mengingat ketiga konsep ini, Anda akan mudah menjadi bingung saat mempelajarinya. Karena konsepnya mirip, mereka sebenarnya menjawab tiga jenis pertanyaan yang sangat berbeda.
Cara yang lebih praktis untuk memahaminya adalah:
Task: Cara memulai misiTask.detached: Seberapa erat tugas tersebut terikat dengan konteks saat iniMainActor: Dalam semantik isolasi apa logika ini harus dieksekusi?
Pisahkan saja ketiga dimensi ini dan banyak kebingungan akan segera hilang.
1. Task memecahkan: Bagaimana cara memasukkan proses asinkron dari sini?
Skenario penggunaan paling alami untuk Task adalah:
Kode saat ini bukan
async, tapi saya harus memasukkan proses asinkron sekarang.
Ini sangat umum di iOS:
- Panggilan balik klik tombol bukan
async - Metode proksi UIKit bukan
async - Peristiwa siklus hidup sinkron tertentu tiba-tiba memicu permintaan asinkron
Misalnya:
Button("刷新") {
Task {
await viewModel.reload()
}
}
Arti Task di sini sangat jelas: menjembatani portal interaksi sinkron ke dunia asinkron.
Jadi kunci Task adalah “batas tugas dibuat”.
Jika dituliskan artinya adalah:
- Di sini dimulailah pekerjaan asinkron dengan siklus hidup independen
- itu mungkin dibatalkan
- Ini akan berakhir pada suatu saat
- Konsekuensinya pada akhirnya harus ditanggung oleh seseorang
Hal ini juga menunjukkan bahwa Task tidak bisa hanya dipahami sebagai “satu lapisan dapat digunakan untuk await”.
2. Karakteristik sebenarnya dari Task biasa: Biasanya melanjutkan periode kerja asinkron dalam konteks saat ini
Situasi yang umum adalah memahami Task biasa terlalu “mandiri”, berpikir bahwa segera setelah dibuat, itu tidak ada hubungannya dengan konteks saat ini.
Pemahaman yang lebih akurat dalam bidang teknik seharusnya adalah:
**Task yang umum sering kali merupakan tugas yang diperluas dari konteks saat ini. **
Artinya sering kali ia mewarisi bagian dari lingkungan saat ini, seperti:
- Semantik aktor saat ini
- Konteks tugas saat ini
- Pembatalan hubungan tertentu
- Kecenderungan prioritas saat ini
Jadi ini lebih seperti “memasukkan async di atas logika saat ini” daripada “menciptakan alam semesta baru yang sepenuhnya keluar dari langkah”.
Penting untuk memahami hal ini, karena nanti Anda akan memahami apa sebenarnya Task.detached yang “melepaskan diri”.
3. Task.detached adalah pernyataan independen yang lebih kuat
Banyak artikel menggambarkan Task.detached sebagai versi “lebih maju”, yang dapat dengan mudah menimbulkan bias dalam praktiknya.
Ini lebih berbahaya.
Alasannya jelas: pada intinya, hal ini kecil kemungkinannya untuk mewarisi konteks saat ini.
Artinya, saat menulis:
Task.detached {
...
}
Faktanya, ini mengungkapkan:
- Karya ini tidak ingin terikat secara alami dengan konteks saat ini
- Saya ingin lebih mandiri
- Saya sendiri bersedia mengambil lebih banyak tanggung jawab siklus hidup dan isolasi
Hal ini wajar dalam beberapa skenario, seperti:
- Lakukan pekerjaan pembersihan latar belakang yang tidak ada hubungannya dengan halaman saat ini
- Melakukan tugas-tugas tertentu yang jelas-jelas memerlukan pelepasan diri dari semantik aktor saat ini
- Kerangka kerja atau lapisan infrastruktur tertentu secara eksplisit memerlukan tugas independen
Namun dalam bisnis halaman, yang diinginkan kebanyakan orang adalah sesuatu yang lebih mudah dikelola.
Dan detached sering kali hanya mempersulit pengelolaan.
4. Task.detached mudah disalahgunakan
Karena perasaan pertama yang diberikannya kepada orang adalah “kebebasan”.
Namun dalam sistem yang berjalan bersamaan, harga dari kebebasan sering kali adalah melimpahnya tanggung jawab.
Setelah Anda menggunakan Task.detached, Anda harus menjawab pertanyaan-pertanyaan ini lagi:
-Siapa pemiliknya sekarang?
- Haruskah ini dilanjutkan ketika halaman dihancurkan?
- Jika tugas luar dibatalkan, apakah tetap berjalan? – Bisakah ia langsung mengubah keadaan saat ini setelah kembali?
Jika tidak ada pertanyaan yang terjawab dengan jelas, Task.detached biasanya berakhir dengan pelarian dari tanggung jawab misi.
Jadi prinsip default saya sederhana:
- Gunakan
Taskbiasa untuk lapisan halaman dan ViewModel terlebih dahulu. - Hanya pertimbangkan
Task.detachedjika Anda mengetahui dengan jelas “mengapa perlu melepaskan diri dari konteks saat ini”
5. MainActor sama sekali bukan konsep “memulai tugas”.
Inilah poin yang perlu diklarifikasi secara menyeluruh.
Task dan Task.detached membahas:
Bagaimana tugas asinkron dibuat dan seberapa erat tugas tersebut terikat dengan konteks saat ini.
MainActor membahas:
Di bawah semantik isolasi apa, bagian kode tertentu harus dieksekusi.
Ini bukan “tugas versi thread utama”, juga bukan “Tugas yang khusus digunakan untuk memperbarui UI”. Ini pada dasarnya memberitahu kompiler dan pemanggil:
- Logika ini milik domain isolasi aktor utama
- Ini sangat terkait dengan UI
- Tidak dapat dimodifikasi secara acak dalam konteks bersamaan
Oleh karena itu, fokus MainActor selalu pada “kendala”.
6. Kode terkait UI harus ditanggapi dengan serius MainActor
Situasi yang umum adalah berpikir: Bagaimanapun, pada akhirnya, kita hanya memberikan nilai pada halaman, jadi itu tidak akan terlalu serius.
Masalahnya adalah status UI yang benar-benar kompleks tidak pernah diberi nilai hanya sekali.
Halaman asli sering kali memiliki hal-hal berikut ini:
- status pemuatan
- Daftar data
- Keadaan kosong
- pesan kesalahan
- Beberapa tombol lokal dinonaktifkan
Setelah nilai-nilai ini ditulis oleh beberapa hasil asinkron pada titik waktu yang berbeda, tanpa batasan MainActor yang jelas, masalahnya perlahan-lahan akan terakumulasi menjadi:
- Halaman berkedip sesekali
- Beberapa pembaruan status berada dalam urutan yang aneh
- ViewModel bolak-balik antara logika latar belakang dan logika UI
Oleh karena itu, nilai MainActor tidak hanya untuk “mencegah kesalahan thread”, tetapi juga untuk menetapkan batas kepemilikan yang jelas untuk status UI.
7. Urutan penilaian yang mendekati pertarungan sebenarnya
Jika Anda tidak tahu konsep mana yang akan digunakan saat menulis kode, Anda dapat bertanya pada diri sendiri terlebih dahulu dengan urutan berikut:
1. Apakah saya berada dalam konteks non-asinkron dan perlu memasukkan proses asinkron?
Jika iya, pertimbangkan Task terlebih dahulu.
2. Apakah saya benar-benar membutuhkan tugas ini secara independen dari konteks saat ini?
Hanya pertimbangkan Task.detached jika jawabannya sangat jelas.
Jika hanya “terasa lebih bebas”, biasanya sebaiknya tidak digunakan.
3. Apakah status membaca dan menulis kode ini sangat terkait?
Jika demikian, Anda harus mempertimbangkan MainActor dengan serius daripada menunggu terjadi kesalahan.
Urutan penilaian ini jauh lebih berguna daripada menghafal definisi API karena secara langsung berhubungan dengan masalah nyata yang dihadapi saat menulis kode bisnis.
8. Seperti apa bau tak sedap yang paling umum?
Tiga jenis bau tak sedap yang paling umum saya lihat adalah:
1. Jika await tidak tersedia, kemas satu Task terlebih dahulu
Hal ini akan membuat entri tugas dalam proyek semakin terfragmentasi, dan pada akhirnya tidak ada yang tahu siapa pemilik tugas tersebut.
2. Tidak memahami pewarisan konteks dan selalu menggunakan Task.detached
Hal ini terlihat sangat “independen”, namun nyatanya sering kali hanya memperparah masalah siklus hidup.
3. ViewModel bertanggung jawab atas pemrosesan latar belakang dan penulisan balik UI, namun tidak ada batasan MainActor yang jelas
Jenis kode ini belum tentu crash dalam jangka pendek, namun sangat rentan terhadap akumulasi kesalahan status tersembunyi dalam jangka panjang.
9. Kesimpulan: Mereka tidak berada dalam dimensi yang sama
Jika saya harus mengingat ketiga konsep ini dalam satu kalimat, saya akan mengatakan ini:
Task: Sekarang saya ingin membuat tugas asinkronTask.detached: Saya ingin membuat tugas asinkron yang lebih independen dan tidak terlalu diwarisi dari konteks saat ini.MainActor: Logika ini harus dijalankan dalam batas isolasi aktor utama
Mereka menjawab pertanyaan berbeda dalam sistem bersamaan:
- Di mana misinya dimulai?
- Seberapa erat tugas tersebut terikat dengan konteks saat ini
- Negara bagian mana yang harus diisolasi oleh aktor utamanya
Selama ketiga dimensi ini dipisahkan, saat menulis kode Swift Concurrency nanti, “memulai tugas” dan “kembali ke thread utama” tidak lagi tertukar dengan hal yang sama.
What to read next
Want more posts about Swift Concurrency?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to keep following #Swift Concurrency?
Tags are useful for related tools, specific problems, and similar troubleshooting notes.
View same tagWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home