Mencampur utas dan coroutine di Unity3D Mobile

8

Saya memiliki coroutine di Unity3D yang mengunduh zip dari server, mengekstraknya ke jalur data persisten, dan memuat isinya ke dalam memori. Alirannya terlihat seperti ini:

IEnumerator LongCoroutine()
{
    yield return StartCoroutine(DownloadZip());
    ExtractZip();
    yield return StartCoroutine(LoadZipContent());
}

Tetapi ExtractZip()metode (yang menggunakan perpustakaan DotNetZip), adalah sinkron, memakan waktu terlalu lama, dan tidak meninggalkan saya tempat untuk menghasilkan selama proses.

Ini mengakibatkan aplikasi terbunuh (pada perangkat seluler) setiap kali saya mencoba mengekstrak zip besar, yang saya asumsikan karena utas utama menjadi tidak responsif terlalu lama.

Apakah ini sesuatu yang diketahui dilakukan oleh OS seluler (bunuh aplikasi jika bingkai terlalu lama)?

Jadi, saya berasumsi bahwa mengekstraksi zip pada utas terpisah mungkin bisa menyelesaikan masalah, dan tampaknya berhasil. Saya membuat kelas ini:

public class ThreadedAction
{
    public ThreadedAction(Action action)
    {
        var thread = new Thread(() => {
            if(action != null)
                action();
            _isDone = true;
        });
        thread.Start();
    }

    public IEnumerator WaitForComplete()
    {
        while (!_isDone)
            yield return null;
    }

    private bool _isDone = false;
}

Dan saya menggunakannya seperti ini:

IEnumerator LongCoroutine()
{
    yield return StartCoroutine(DownloadZip());
    var extractAction = new ThreadedAction(ExtractZip);
    yield return StartCoroutine(extractAction.WaitForComplete());
    yield return StartCoroutine(LoadZipContent());
}

Tapi saya masih tidak yakin apakah ini cara terbaik untuk mengimplementasikannya, atau apakah saya perlu mengunci _isDone(tidak terlalu terbiasa dengan multithreading).

Adakah yang salah dengan ini? Apakah saya melewatkan sesuatu?

David Gouveia
sumber

Jawaban:

4

Ini adalah solusi yang sangat elegan untuk menyelesaikan tugas multithread di coroutine, dilakukan dengan baik :)

Mencampur coroutine dan utas sangat aman, asalkan Anda mengunci akses ke sumber daya yang dibagikan dengan benar antara utas utama Anda (yang dijalankan coroutine) dan utas pekerja yang Anda buat. Anda tidak perlu mengunci _isDone dengan cara apa pun, karena ini hanya ditulis oleh utas pekerja dan tidak ada status perantara yang dapat menyebabkan utas utama berperilaku tidak pantas.

Di mana Anda perlu mencari kemungkinan masalah, adalah jika ada sumber daya yang ditulis oleh ExtractZip dan keduanya

  1. ditulis bersamaan oleh suatu fungsi yang dipanggil dari utas utama Anda atau
  2. sedang dibaca oleh fungsi di utas utama dan diharapkan dalam keadaan aman sebelum ExtractZip selesai.

Dalam kasus khusus ini, kekhawatiran saya adalah jika Anda tidak memeriksa bahwa Anda tidak mencoba mengunduh file yang sama ke lokasi yang sama dua kali, Anda dapat memiliki dua utas yang secara bersamaan menjalankan ExtractZip yang saling mengganggu.

FlintZA
sumber
1

Ada solusi gratis untuk ini di Toko Aset:

Utas Ninja

Dengan itu Anda dapat beralih di antara utas utama & utas latar belakang dengan mudah, yang suka:

void Awake() {
    this.StartCoroutineAsync(AsyncCouroutine());
}

IEnumerator AsyncCoroutine() {
    // won't block
    Thread.Sleep(10000);

    yield return Ninja.JumpToUnity;

    // we're now on Unity's main thread
    var time = Time.time;

    yield return Ninja.JumpBack;

    // now on background thread again
    // ...
Ran QUAN
sumber
-1

Yah, saya bukan ahli utama juga, tapi saya pikir dalam contoh ke-2 Anda, fungsi WaitForComplete () masih akan memblokir utas panggilan sampai utas ThreadedAction selesai. Apa yang dapat Anda lakukan adalah meneruskan fungsi callback ke Thread pemrosesan zip Anda, dan minta fungsi itu dipanggil saat selesai. Dengan begitu utas utama Anda memulai utas zip dan kemudian melanjutkan melakukan tugasnya (memperbarui GUI, apa pun yang Anda lakukan), dan kemudian fungsi panggil balik akan menetapkan sesuatu di utas utama ketika sudah selesai (seperti boolean zipDone = true), dan pada titik ini utas utama dapat bereaksi terhadap itu (tampilan "Zip diekstraksi" di GUI dll).

Shivan Dragon
sumber
2
Akan muncul begitu, tetapi WaitForComplete adalah coroutine Unity3D, jadi itu hanya akan menjalankan satu iterasi dari while loop setiap frame, kemudian menghasilkan untuk membiarkan segala sesuatu yang lain dalam pembaruan game. Itu tidak menghalangi utas :)
David Gouveia
-2

Anda tidak perlu mengunci _isDone, tetapi itu harus ditandai volatile.

Jess Winter
sumber