熊屋 | 技術小記

iOS, Web Development Notes

Concurrency - 確保退出目前的 Thread 並不繼續執行

| Comments

要退出某個 thread 很簡單,但是還是要注意雖然退出了,有執行時間差的關係,還是會去執行到原先的 statements 。

解法

NSThread 中,如果使用 exit 潛在會造成 leaking 資源,因此應該要使用 cancel 來退出 thread 。

1
2
NSThread *thread = /* 取得 thread 的 reference */;
[thread cancel];

實際使用

在專案中實際使用的時候,如非同步處理不同的運算,就算一個 thread 被 cancel 了,他在退出前還是會把這個執行緒該做的事情做完。

這裡有個範例的程式碼,今天所有的程式碼都會在 appDelegate.m 裡面撰寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.myThread = [[NSThread alloc] initWithTarget:self
                                            selector:@selector(threadEntryPoint)
                                              object:nil];

    // 在 Delay 3 秒鐘之後,離開 myThread
    [self performSelector:@selector(stopThread) withObject:nil afterDelay:3.0f];

    [self.myThread start];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

#pragma mark - Thread Operations

- (void)threadEntryPoint
{
    @autoreleasepool {
        NSLog(@"Thread Entry Point");

        while ([[NSThread currentThread] isCancelled] == NO) {

            // 每 sleep 4 秒才會被 fired
            [NSThread sleepForTimeInterval:4.0f];

            NSLog(@"Thread Loop");
        }

        NSLog(@"Thread Finished");
    }
}

- (void)stopThread {
    NSLog(@"Thread Canceling");
    [self.myThread cancel];

    NSLog(@"Thread Releasing");
    self.myThread = nil;
}

這裡做的事情是:

  1. self.myThread 被初始化的時候,就指定要在這個 thread 執行 - threadEntryPoint 這個 method 。
  2. 這時候因為以上第 29 行的 sleep 4 秒,每四秒才會印一次 “Thread Loop”
  3. self.myThread 初始化後,自己在另外一個 thread 讀秒的時候,在原有的 thread 上指定 delay 3 秒之後退出 self.myThread
  4. 開始執行 self.myThread ,這時候還沒有 3 秒,所以 while 的條件滿足,因此會執行 while 的內容。這裡的 currentThread 就是指 self.myThread ,因為在 thread 初始化的時候就被指定了。

初步執行結果:

1
2
3
4
5
6
7
...
Thread Entry Point
Thread Canceling
Thread Releasing
Thread Loop
Thread Finished
...

這時候就會發現,雖然已經 cancelled 甚至 released ,在最後還是會印出 thread 中執行的兩個 NSLog。就印證就算已經退出了一個 thread ,thread 原有的程式還是會執行完的。

目前為止的程式碼

解決辦法

這個情形其實滿常見的,雖然在 while 的時候會去檢查 thread 是不是已經被 cancelled 了,但是當在經過一段時間等待之後、實際執行之前,並沒有檢查是不是已經被 cancelled 了。

因此只要在等待之後,開始執行該執行的 statement 的之前,加上判斷就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)threadEntryPoint
{
    @autoreleasepool {
        NSLog(@"Thread Entry Point");

        while ([[NSThread currentThread] isCancelled] == NO) {

            [NSThread sleepForTimeInterval:4.0f];

            // 執行前先判斷是否已經被 cancelled 了
            if ([[NSThread currentThread] isCancelled] == NO) {
                NSLog(@"Thread Loop");
            }

        }

        NSLog(@"Thread Finished");
    }
}

再次執行就會看到, while loop 裡面的東西經過判斷後,就不會被跑到了:

1
2
3
4
5
6
...
Thread Entry Point
Thread Canceling
Thread Releasing
Thread Finished
...

最後的程式碼

範例 Repo

我有把這篇文章的專案在 Github 建一個 repo ,可以 clone 下來玩玩看 :D

Repo: https://github.com/kumayast/Exiting-Thread

本篇文章撰寫時使用 Xcode 5.1.1 以及 iOS SDK 7.1 on Mac OSX 10.10 beta 2 ,如果發現相關內容有更新,請在下方留言,謝謝。

Comments