iPhone: bagaimana cara menggunakan metode performSelector:onThread:withObject:waitUntilDone:?

Saya mencoba menggunakan utas terpisah untuk bekerja dengan beberapa API.

Masalahnya adalah saya tidak dapat menggunakan metode performSelector:onThread:withObject:waitUntilDone: dengan thread yang saya buat untuk ini.

Kode saya:

@interface MyObject : NSObject {
  NSThread *_myThread;
}
@property(nonatomic, retain) NSThread *myThread;
@end

@implementation MyObject
@synthesize myThread = _myThread;
- (NSThread *)myThread {
  if (_myThread == nil) {
    NSThread *myThreadTemp = [[NSThread alloc] init];
    [myThreadTemp start];
    self. myThread = myThreadTemp;
    [myThreadTemp release];
  }
  return _myThread;
}

- (id)init {
  if (self = [super init]) {
    [self performSelector:@selector(privateInit:) onThread:[self myThread] withObject:nil waitUntilDone:NO];
  }
  return self;
}
- (void)privateInit:(id)object {
  NSLog(@"MyObject - privateInit start");
}

- (void)dealloc {
  [_myThread release];
  _myThread = nil;
  [super dealloc];
}
@end

"MyObject - privateInit start" tidak pernah dicetak.
Apa yang saya lewatkan?

Saya mencoba membuat instance thread dengan target dan pemilih, mencoba menunggu penyelesaian eksekusi metode (waitUntilDone:YES).
Tidak ada yang membantu.

PEMBARUAN:
Saya tidak memerlukan multithreading ini untuk memisahkan operasi mahal ke thread lain.
Dalam hal ini saya bisa menggunakan performSelectorInBackground seperti yang disebutkan dalam beberapa jawaban.
Alasan utama untuk thread terpisah ini adalah kebutuhan untuk melakukan semua tindakan di API (TTS oleh Loquendo) dari satu thread.
Artinya saya harus membuat instance objek TTS dan memanggil metode pada objek tersebut dari thread yang sama sepanjang waktu .


person Michael Kessler    schedule 06.04.2010    source sumber


Jawaban (4)


Saya menemukan jawabannya!

Untuk mempertahankan thread ini, diperlukan potongan kode tambahan:

- (void)threadMain:(id)data {
    NSAutoreleasePool *pool = [NSAutoreleasePool new];

    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

    while (isAlive) { // 'isAlive' is a variable that is used to control the thread existence...
        [runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }

    [pool release];
}


Dan baris berikutnya:

NSThread *myThreadTemp = [[NSThread alloc] init];

Harus diganti dengan yang ini:

NSThread *myThreadTemp = [[NSThread alloc] initWithTarget:self selector:@selector(threadMain:) object:nil];

EDIT: Seperti yang disarankan oleh beberapa orang di sini saya telah menambahkan beberapa baris kode (NSAutoreleasePool, metode addPort dan boolean 'isAlive').

person Michael Kessler    schedule 10.04.2010
comment
Bukankah menciptakan loop tak terbatas seperti itu? Jika runloop tidak ada hubungannya, runloop akan segera berhenti dan yang Anda lakukan hanyalah memanggil - run sesering yang dapat ditangani oleh prosesor Anda. (Penggunaan CPU pada 100%) - person bastibe; 13.04.2010
comment
Tak terbatas - ya. Namun perulangan tidak berulang sepanjang waktu. Saya telah meletakkan baris log (NSLog...) di dalam while dan itu dipanggil hanya sekali... - person Michael Kessler; 14.04.2010
comment
Ini adalah perilaku yang tidak terduga dan tidak boleh diandalkan. Perulangan yang berjalan tanpa sumber input atau pengatur waktu yang dikonfigurasi harus segera keluar, dan oleh karena itu perulangan Anda harus berjalan terus-menerus, berulang-ulang, menggunakan CPU 100%, seperti yang dikatakan dalam poster. Lihat dokumen nsrunloop untuk info lebih lanjut. - person Benji XVI; 13.07.2010
comment
Silakan mencoba solusinya sebelum menyatakan pernyataan tersebut dan, terutama, sebelum menurunkan reputasi seseorang. Tidak ada CPU 100% yang digunakan. - person Michael Kessler; 13.07.2010
comment
Seperti yang saya katakan, dokumentasi dengan jelas menyatakan bahwa NSRunLoop harus segera keluar dan 100% cpu harus digunakan. Kode Anda menunjukkan perilaku yang tidak terduga – baik mengeksploitasi bug agar berfungsi, atau berfungsi karena alasan lain yang tidak terwakili dalam kode di sini. Pemrogram yang baik akan sangat prihatin dengan hal ini, dan tidak langsung merespons dengan kode yang tampaknya berhasil. Dokumentasi secara eksplisit menyatakan perilaku sebaliknya. - person Benji XVI; 13.07.2010
comment
Meskipun saya tidak senang mengurangi perwakilan Anda, mengabaikan jawaban ini diperlukan untuk mencegah orang lain melihat kode ini dan berpikir itu adalah solusi yang baik. Hal ini tidak terjadi, karena 1. mengeksploitasi perilaku tidak berdokumen dan 2. tidak mengizinkan penghentian thread secara bersih. Jika Anda ingin saya menghapus suara negatif, baca dokumentasinya, terima kritik orang lain, dan edit jawaban Anda dengan solusi yang baik. - person Benji XVI; 13.07.2010
comment
Perhatikan bahwa utas juga harus menyiapkan kumpulan rilis otomatisnya sendiri, sesuai jawaban KermiDT. Untuk membuat jawaban ini dapat diterima, 1. tambahkan itu, 2. tambahkan sistem keepalive untuk menghilangkan ketergantungan pada perilaku tidak terdokumentasi (sesuai KermiDT: tambahkan pemilih / kotenok-gav: tambahkan port), 3. ubah loop while untuk memeriksa kondisi keluar – do { ... } while (!shouldExit) – dan 4. tambahkan kode pembersihan (rilis autoreleasepool dan variabel lainnya) setelah loop. - person Benji XVI; 13.07.2010
comment
Berikut adalah proyek xcode yang mengilustrasikan perilaku yang diharapkan. Metode yang relevan adalah threadMain di MyController.m. Ketika baris yang menambahkan port dummy ke loop run dikomentari, thread dipatok pada 100%. Mengapa hal ini tidak terjadi pada kode Anda sendiri tidak jelas dari kode yang diposting di sini, tetapi Anda tidak bijaksana untuk melanjutkan terlalu jauh ke rute multithreading tanpa menyelidikinya dengan benar. - person Benji XVI; 13.07.2010
comment
@Benji, saya telah memeriksa proyek Anda dan memeriksa solusi saya sekali lagi dan sampai pada kesimpulan bahwa 'addPort' hanya diperlukan di Mac (karena ini dihasilkan dari banyak proses yang telah saya lakukan). Mungkin di iPhone hal ini dilakukan di latar belakang untuk mencegah penemu seperti saya merusak aplikasi iPhone. Tentang NSAoutoreleasePool: Saya biasa memulai dan melepaskannya di setiap metode yang dijalankan di utas terpisah - sekarang saya mengerti bahwa cara ini jauh lebih mudah. Tentang sementara (benar): tentu saja saya tidak membiarkannya seperti ini di aplikasi saya - hanya lupa memperbarui jawabannya. Lihat jawaban saya yang diperbarui... - person Michael Kessler; 14.07.2010
comment
Itu cukup menarik tentang proyek iPhone, saya akan melihatnya. Bahkan Dokumen NSRunLoop untuk iPhone (lihat metode run...) mengatakan bahwa run loop harus segera keluar ketika tidak ada sumber atau pengatur waktu. - person Benji XVI; 14.07.2010
comment
Oke, saya mendapatkan perilaku yang diharapkan (utas menggunakan 100%) dengan porting sepele dari proyek xcode di atas ke iPhone. Namun baris yang saat ini Anda komentari sebagai tidak berpengaruh di iPhone pasti diperlukan, biasanya. (Edit: proyek xcode iphone - pantau top sambil menjalankannya untuk melihat efeknya.) - person Benji XVI; 14.07.2010
comment
Saya telah mengunduh proyek Anda dan menjalankannya. Runloop HANYA berjalan ketika ada tindakan yang dilakukan pada thread tersebut. Saya telah menambahkan satu tombol lagi dan melakukan pemilih pada utas. Runloop kembali HANYA ketika pemilih berakhir setiap saat. Lihat tangkapan layar (dl.dropbox.com/u/2390939/Picture%2011.png dan dl.dropbox.com/u/2390939/Picture%2010 .png). - person Michael Kessler; 15.07.2010
comment
Menarik. Coba hal yang sama, tetapi hapus panggilan NSLog dan sebagai gantinya pantau top – utasnya berputar tanpa batas. Untuk beberapa alasan, melakukan panggilan NSLog sebelum (atau sesudah, dalam loop!) [loop run] akan mencegah diperolehnya perilaku yang tepat. Tidak ada penjelasan untuk ini, kecuali baris dari Referensi Fungsi Fondasi ini sepertinya memberi kesan: Output from NSLogv is serialized, in that *only one thread in a process can be doing the writing/logging described above at a time*. All attempts at writing/logging a message complete before the next thread can begin its attempts. - person Benji XVI; 16.07.2010
comment
Ah, ini dia – seseorang yang mengidentifikasi interaksi yang sama, antara NSLog dan NSRunLoop, dan mendapatkan (semacam) jawaban. NSLog sedang menyiapkan port tersembunyi, yang mencegah kembalinya run loop – oleh karena itu, port ini diserialkan, meskipun kami tidak dapat memastikannya karena sumbernya bukan untuk publik. Tolong bisakah Anda menghapus baris yang mengatakan ini hanya mempengaruhi mac – ini tidak akurat dan saya khawatir seseorang akan melihatnya! - person Benji XVI; 16.07.2010
comment
Selesai. Saya menghargai penelitian Anda. Apakah Anda sedang mencari pekerjaan? :) - person Michael Kessler; 16.07.2010

Inilah yang berhasil untuk saya. Loop utama diambil dari dokumentasi Apple http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW25

- (void) start {
    self.imageSaverThread = [[[NSThread alloc] initWithTarget:self selector:@selector(imageSaverThreadMain) object:nil] autorelease];
    [self.imageSaverThread start];
}

- (void) imageSaverKeepAlive {
    [self performSelector:@selector(imageSaverKeepAlive) withObject:nil afterDelay:60];    
}

- (void)imageSaverThreadMain
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // Add selector to prevent CFRunLoopRunInMode from returning immediately
    [self performSelector:@selector(imageSaverKeepAlive) withObject:nil afterDelay:60];
    BOOL done = NO;

    do
    {
        NSAutoreleasePool *tempPool = [[NSAutoreleasePool alloc] init];
        // Start the run loop but return after each source is handled.
        SInt32    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);

        // If a source explicitly stopped the run loop, or if there are no
        // sources or timers, go ahead and exit.
        if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
            done = YES;

        [tempPool release];
    }
    while (!done);

    [pool release];
}

Semoga ini bisa membantu

person Przemyslaw Zych    schedule 13.07.2010
comment
Sekarang ini adalah bantuan. Perhatikan bagaimana beberapa jenis acara harus ditambahkan untuk mencegah run loop segera kembali. - person Benji XVI; 13.07.2010

Yah, saya kira saya punya solusi yang lebih baik

- (void)run{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    running = true;
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    while (running && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]){
        //run loop spinned ones
    }

    [pool release];
}

Apa yang saya lakukan disini?
1) Menambahkan port tiruan di sini sebagai Sumber akan mencegah metode runMode:beforeDate: segera keluar.
2) Metode runMode:beforeDate: memblokir thread hingga ada sesuatu di runLoop.

person Andrey Lushnikov    schedule 07.05.2010
comment
Jika saya memanggil performSelector:onThread: beberapa kali, apakah semua pemilih saya mengantri? - person onmyway133; 08.07.2013

Anda telah membuat thread, namun thread tersebut tidak berjalan. Itu harus dijalankan untuk menjalankan sesuatu.

Anda juga dapat menggunakan "performSelectorInBackground" sebagai gantinya. Ini akan mengantri pemanggilan sampai inisialisasi selesai.

person Laurent Etiemble    schedule 06.04.2010
comment
Terima kasih atas komentarmu. [myThreadTemp start]; bukankah seharusnya membuat thread berjalan? performSelectorInBackground tidak baik untukku. Saya harus menggunakan satu utas untuk semua operasi pada API yang saya gunakan (TTS oleh Loquendo). - person Michael Kessler; 06.04.2010
comment
Dalam waktu antara [myThreadTemp start]; dan pemilih kinerja Anda, thread mungkin telah berhenti. - person Giao; 06.04.2010
comment
Jika Anda memerlukan utas terpisah, Anda dapat menggunakan detachNewThreadSelector:toTarget:withObject:. Ini akan membuat dan menelurkan thread baru yang akan segera dimulai, jadi Anda tidak perlu peduli dengan pembuatan NSThread. - person Laurent Etiemble; 06.04.2010
comment
Masalahnya adalah saya harus menyimpan referensi ke thread tersebut. Saya menggunakan API yang disebutkan sepanjang waktu dan ingin menjalankan SEMUA tindakan pada API tersebut dari thread yang sama. Jika saya menggunakan metode detachNewThreadSelector maka saya tidak akan mendapatkan referensinya. Saya percaya bahwa saya bisa mendapatkan referensi dari metode yang dieksekusi tetapi saya yakin thread masih akan dihentikan setelah eksekusi metode selesai. - person Michael Kessler; 06.04.2010
comment
Satu hal lagi - jika performSelector:onThread:withObject:waitUntilDone: ada maka harus ada cara untuk menggunakannya. Saya tidak mengerti bagaimana melakukannya... - person Michael Kessler; 06.04.2010