iPhone: วิธีใช้ PerformSelector: onThread:withObject:waitUntilDone: วิธีการ?

ฉันกำลังพยายามใช้เธรดแยกต่างหากสำหรับการทำงานกับ API บางตัว

ปัญหาคือฉันไม่สามารถใช้วิธี performSelector:onThread:withObject:waitUntilDone: กับเธรดที่ฉันสร้างอินสแตนซ์ไว้ได้

รหัสของฉัน:

@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" ไม่เคยพิมพ์
ฉันขาดอะไรไป

ฉันพยายามสร้างอินสแตนซ์ของเธรดด้วยเป้าหมายและตัวเลือก พยายามรอให้การดำเนินการเมธอดเสร็จสิ้น (waitUntilDone:YES)
ไม่มีอะไรช่วย

อัปเดต:
ฉันไม่ต้องการมัลติเธรดนี้เพื่อแยกการดำเนินการที่มีค่าใช้จ่ายสูงไปยังเธรดอื่น
ในกรณีนี้ ฉันสามารถใช้ performSelectorInBackground ตามที่กล่าวไว้ในคำตอบไม่กี่ข้อ
เหตุผลหลักสำหรับเธรดแยกนี้ คือความจำเป็นในการดำเนินการทั้งหมดใน API (TTS โดย Loquendo) จากเธรดเดียว
หมายความว่าฉันต้องสร้างอินสแตนซ์ของวัตถุ TTS และเรียกใช้วิธีการบนวัตถุนั้นจากเธรดเดียวกันตลอดเวลา .


person Michael Kessler    schedule 06.04.2010    source แหล่งที่มา


คำตอบ (4)


ฉันพบคำตอบแล้ว!

เพื่อที่จะรักษาเธรดให้คงอยู่ จำเป็นต้องมีโค้ดเพิ่มเติม:

- (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];
}


และบรรทัดถัดไป:

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

ควรแทนที่ด้วยสิ่งนี้:

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

แก้ไข: ตามที่แนะนำโดยคนไม่กี่คนที่นี่ ฉันได้เพิ่มโค้ดสองสามบรรทัด (NSAutoreleasePool, วิธี addPort และบูลีน 'isAlive')

person Michael Kessler    schedule 10.04.2010
comment
ไม่ได้สร้างวงวนไม่สิ้นสุดแบบนั้นเหรอ? หาก runloop ไม่มีอะไรให้ทำ มันก็จะออกทันที และสิ่งที่คุณทำคือเรียก - run ให้บ่อยเท่าที่ตัวประมวลผลของคุณสามารถจัดการได้ (การใช้งาน CPU ที่ 100 %) - person bastibe; 13.04.2010
comment
อนันต์ - ใช่ แต่การวนซ้ำไม่ได้วนซ้ำตลอดเวลา ฉันใส่บรรทัดบันทึก (NSLog...) ไว้ในขณะนั้นและมันถูกเรียกเพียงครั้งเดียว... - person Michael Kessler; 14.04.2010
comment
นี่เป็นพฤติกรรมที่ไม่คาดคิด และไม่ควรเชื่อถือ รันลูปที่ไม่มีแหล่งอินพุตหรือตัวจับเวลาที่กำหนดค่าควรออกทันที ดังนั้นลูปของคุณควรทำงานอย่างต่อเนื่อง ซ้ำแล้วซ้ำเล่า โดยใช้ CPU 100% ดังที่ผู้โพสต์กล่าวไว้ อ้างถึงเอกสาร nsrunloop สำหรับข้อมูลเพิ่มเติม - person Benji XVI; 13.07.2010
comment
โปรดลองวิธีแก้ปัญหาก่อนที่จะระบุข้อความดังกล่าว และโดยเฉพาะอย่างยิ่ง ก่อนที่จะลดทอนชื่อเสียงของผู้อื่น ไม่มีการใช้ CPU 100% - person Michael Kessler; 13.07.2010
comment
ดังที่ฉันกล่าวไว้ เอกสารระบุอย่างชัดเจนว่า NSRunLoop ควรออกทันที และควรใช้ cpu 100% รหัสของคุณแสดงพฤติกรรมที่ไม่คาดคิด ไม่ว่าจะใช้ประโยชน์จากจุดบกพร่องในการทำงาน หรือทำงานเนื่องจากเหตุผลอื่นที่ไม่ได้แสดงในโค้ดที่นี่ โปรแกรมเมอร์ที่ดีจะกังวลอย่างมากกับสิ่งนี้ ดูเหมือนว่าโค้ดจะไม่ตอบสนองอย่างโผงผาง เอกสารระบุถึงพฤติกรรมที่ขัดแย้งอย่างชัดเจน - person Benji XVI; 13.07.2010
comment
แม้ว่าฉันจะไม่มีความสุขเลยที่จะลดจำนวนตัวแทนของคุณ แต่การลดคำตอบนี้ลงก็เป็นสิ่งจำเป็นเพื่อป้องกันไม่ให้คนอื่นเห็นโค้ดนี้และคิดว่ามันเป็นทางออกที่ดี ไม่ใช่ เนื่องจาก 1. ใช้ประโยชน์จากพฤติกรรมที่ไม่มีเอกสาร และ 2. ไม่อนุญาตให้มีการยกเลิกเธรดโดยสิ้นเชิง หากคุณต้องการให้ฉันลบ downvote โปรดอ่านเอกสาร รับฟังคำวิพากษ์วิจารณ์ของผู้คน และแก้ไขคำตอบของคุณด้วยวิธีแก้ปัญหาที่ดี - person Benji XVI; 13.07.2010
comment
โปรดทราบว่าเธรดควรตั้งค่ากลุ่มการเผยแพร่อัตโนมัติของตนเองตามคำตอบของ KermiDT เพื่อให้คำตอบนี้เป็นที่ยอมรับ 1. เพิ่มสิ่งนั้น 2. เพิ่มระบบ Keepalive เพื่อยกเลิกการพึ่งพาพฤติกรรมที่ไม่มีเอกสาร (ตาม KermiDT: เพิ่มตัวเลือก / kotenok-gav: เพิ่มพอร์ต) 3. เปลี่ยน while วนซ้ำ เพื่อตรวจสอบเงื่อนไขการออก - do { ... } while (!shouldExit) - และ 4. เพิ่มโค้ดการล้างข้อมูล (ปล่อย autoreleasepool และตัวแปรอื่น ๆ ) หลังการวนซ้ำ - person Benji XVI; 13.07.2010
comment
นี่คือโปรเจ็กต์ xcode ที่แสดงให้เห็นลักษณะการทำงานที่คาดหวัง วิธีการที่เกี่ยวข้องคือ threadMain ใน MyController.m เมื่อบรรทัดที่เพิ่มพอร์ตจำลองไปยังรันลูปถูกใส่เครื่องหมายไว้ เธรดจะถูกตรึงที่ 100% เหตุใดสิ่งนี้จึงไม่เกิดขึ้นในรหัสของคุณเองจึงไม่ชัดเจนจากรหัสที่โพสต์ที่นี่ แต่คุณไม่ฉลาดที่จะดำเนินการตามเส้นทางมัลติเธรดมากเกินไปโดยไม่ตรวจสอบอย่างถูกต้อง - person Benji XVI; 13.07.2010
comment
@Benji ฉันได้ตรวจสอบโครงการของคุณและตรวจสอบวิธีแก้ปัญหาของฉันอีกครั้งและได้ข้อสรุปว่า 'addPort' จำเป็นใน Mac เท่านั้น (เนื่องจากมันมาจากการรันหลายครั้งที่ฉันได้ทำไปแล้ว) บางทีใน iPhone อาจมีการดำเนินการในเบื้องหลังเพื่อป้องกันไม่ให้นักประดิษฐ์เช่นฉันทำลายแอป iPhone เกี่ยวกับ NSAoutoreleasePool: ฉันเคยเริ่มต้นและปล่อยมันในแต่ละวิธีที่ดำเนินการในเธรดที่แยกจากกัน - ตอนนี้ฉันเข้าใจแล้วว่าวิธีนี้ง่ายกว่ามาก ในช่วงเวลาหนึ่ง (จริง): แน่นอนว่าฉันไม่ได้ทิ้งมันไว้แบบนี้ในแอปของฉัน - แค่ลืมอัปเดตคำตอบ ดูคำตอบที่อัปเดตของฉัน... - person Michael Kessler; 14.07.2010
comment
โปรเจ็กต์ iPhone ค่อนข้างน่าสนใจ ฉันจะลองดู แม้แต่ เอกสาร NSRunLoop สำหรับ iPhone (ดูวิธีการเรียกใช้...) บอกว่า run loop ควรออกทันทีเมื่อไม่มีแหล่งที่มาหรือตัวจับเวลาอยู่ - person Benji XVI; 14.07.2010
comment
ตกลง ฉันได้รับพฤติกรรมที่คาดหวัง (เธรดใช้ 100%) ด้วยการย้ายโครงการ xcode ข้างต้นไปยัง iPhone เล็กน้อย แต่บรรทัดที่คุณแสดงความคิดเห็นในปัจจุบันว่าไม่มีผลกระทบใน iPhone แน่นอนว่าเป็นสิ่งจำเป็น ตามปกติ (แก้ไข: โครงการ xcode iphone - ตรวจสอบ top ในขณะที่เรียกใช้งานเพื่อดูเอฟเฟกต์) - person Benji XVI; 14.07.2010
comment
ฉันได้ดาวน์โหลดโครงการของคุณและเรียกใช้แล้ว runloop จะทำงานเมื่อมีการดำเนินการกับเธรดนั้นเท่านั้น ฉันได้เพิ่มอีกหนึ่งปุ่มและดำเนินการตัวเลือกบนเธรด runloop ส่งคืนเฉพาะเมื่อตัวเลือกสิ้นสุดในแต่ละครั้ง ดูภาพหน้าจอ (dl.dropbox.com/u/2390939/Picture%2011.png และ dl.dropbox.com/u/2390939/Picture%2010 .png) - person Michael Kessler; 15.07.2010
comment
น่าสนใจ. ลองสิ่งเดียวกัน แต่ลบการเรียก NSLog ออกและมอนิเตอร์ top แทน - เธรดจะวนซ้ำอย่างไม่สิ้นสุด ด้วยเหตุผลบางประการการโทร NSLog ใด ๆ ก่อน (หรือหลังในลูป!) [loop run] จะป้องกันไม่ให้ได้รับพฤติกรรมที่เหมาะสม ไม่มีคำอธิบายสำหรับสิ่งนี้ ยกเว้นบรรทัดนี้จากการอ้างอิงฟังก์ชันพื้นฐานดูเหมือนว่าจะมีการชี้นำ: 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
อ่า เอาล่ะ – บุคคลที่ระบุตัวตนของ การโต้ตอบเดียวกันระหว่าง NSLog และ NSRunLoop และรับคำตอบ (ประเภท) NSLog กำลังตั้งค่าพอร์ตที่ซ่อนอยู่ ซึ่งจะป้องกันไม่ให้ run loop กลับมา ดังนั้นจึงมีการทำให้เป็นอนุกรม แม้ว่าเราจะยืนยันไม่ได้เนื่องจากแหล่งที่มาไม่เปิดเผยต่อสาธารณะ โปรดลบบรรทัดที่บอกว่าสิ่งนี้มีผลกับ Mac เท่านั้น มันไม่ถูกต้องและฉันกังวลว่าจะมีคนเห็นมัน! - person Benji XVI; 16.07.2010
comment
เสร็จแล้ว. ฉันขอขอบคุณการวิจัยของคุณ คุณกำลังมองหางาน? :) - person Michael Kessler; 16.07.2010

นี่คือสิ่งที่ใช้ได้ผลสำหรับฉัน ลูปหลักที่นำมาจากเอกสารของ 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];
}

หวังว่ามันจะช่วยได้

person Przemyslaw Zych    schedule 13.07.2010
comment
ตอนนี้นี่คือความช่วยเหลือ โปรดทราบว่าควรเพิ่มเหตุการณ์บางประเภทเพื่อป้องกันไม่ให้การวนซ้ำกลับมาทันที - person Benji XVI; 13.07.2010

ฉันคิดว่าฉันมีทางออกที่ดีกว่า

- (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];
}

ฉันมาทำอะไรที่นี่?
1) การเพิ่มพอร์ตจำลองที่นี่เป็น Source จะป้องกันไม่ให้เมธอด runMode:beforeDate: ออกทันที
2) เมธอด runMode:beforeDate: บล็อกเธรดจนกว่าจะมีบางอย่างใน runLoop

person Andrey Lushnikov    schedule 07.05.2010
comment
ถ้าฉันเรียก PerformSelector:onThread: หลายครั้ง ตัวเลือกทั้งหมดของฉันจะเข้าคิวหรือไม่ - person onmyway133; 08.07.2013

คุณได้สร้างเธรดแล้ว แต่เธรดไม่ได้ทำงานอยู่ มันจะต้องทำงานเพื่อที่จะดำเนินการบางอย่าง

คุณสามารถใช้ "performSelectorInBackground" แทนได้ มันจะจัดคิวการร้องขอจนกว่าการเริ่มต้นจะเสร็จสิ้น

person Laurent Etiemble    schedule 06.04.2010
comment
ขอบคุณสำหรับความคิดเห็นของคุณ. [myThreadTemp start]; ไม่ควรทำให้เธรดทำงานใช่ไหม performSelectorInBackground ไม่ดีสำหรับฉัน ฉันต้องใช้หนึ่งเธรดสำหรับการดำเนินการทั้งหมดบน API ที่ฉันใช้ (TTS โดย Loquendo) - person Michael Kessler; 06.04.2010
comment
ในช่วงเวลาระหว่าง [myThreadTemp start]; และตัวเลือกการดำเนินการของคุณ เธรดอาจหยุดทำงาน - person Giao; 06.04.2010
comment
หากคุณต้องการเธรดแยกต่างหาก คุณสามารถใช้ detachNewThreadSelector:toTarget:withObject: มันจะสร้างและสร้างเธรดใหม่ที่จะเริ่มทันที ดังนั้นคุณจึงไม่ต้องกังวลเกี่ยวกับการสร้าง NSThread - person Laurent Etiemble; 06.04.2010
comment
ปัญหาคือฉันต้องเก็บการอ้างอิงถึงเธรด ฉันใช้ API ที่กล่าวถึงตลอดเวลาและต้องการดำเนินการทั้งหมดบน API นั้นจากเธรดเดียวกัน หากฉันใช้วิธี detachNewThreadSelector ฉันจะไม่ได้รับข้อมูลอ้างอิง ฉันเชื่อว่าฉันสามารถได้รับการอ้างอิงจากวิธีการดำเนินการ แต่ฉันแน่ใจว่าเธรดยังคงถูกยกเลิกเมื่อการดำเนินการวิธีการเสร็จสิ้น - person Michael Kessler; 06.04.2010
comment
อีกประการหนึ่ง - หากมี performSelector:onThread:withObject:waitUntilDone: อยู่ ก็ควรจะมีวิธีใช้งาน ฉันไม่เข้าใจว่าต้องทำอย่างไร... - person Michael Kessler; 06.04.2010