Notification and multithreading

Source: Internet
Author: User
Tags notification center

A few days ago with colleagues to discuss the notification in the multi-threaded forwarding problem, so this collation.

Let's take a look at the official documentation, which reads:

In a multithreaded application, notifications is always delivered in the thread in which the notification is posted, whi CH may isn't being the same thread in which an observer registered itself.

The translation comes here:

In a multithreaded application, the thread in which the notification is post, is forwarded in which thread, not necessarily in the thread that registers the observer.

That is, the notification send and receive processing is in the same thread. To illustrate this point, let's look at an example first:

sending and processing of code listing 1:notification

@implementation Viewcontroller-(void) viewdidload {    [super viewdidload];     NSLog (@ "Current thread =%@", [Nsthread CurrentThread]);     [[Nsnotificationcenter Defaultcenter] addobserver:self selector: @selector (handlenotification:) name:test_ NOTIFICATION Object:nil];     Dispatch_async (Dispatch_get_global_queue (dispatch_queue_priority_default, 0), ^{         [[Nsnotificationcenter Defaultcenter] postnotificationname:test_notification object:nil userinfo:nil];    });} -(void) Handlenotification: (nsnotification *) notification{    NSLog (@ "Current thread =%@", [Nsthread CurrentThread] );     NSLog (@ "test notification");} @end
The output results are as follows:
2015-03-11 22:05:12.856 test[865:45102] Current thread = {Number = 1, name = main}2015-03-11 22:05:12.857 test[865:45174] Current thread = {Number = 2, name = (null)}2015-03-11 22:05:12.857 test[865:45174] Test notification

As you can see, although we registered the observer for the notification in the main thread, the notification of the post in the global queue is not processed in the main thread. Therefore, it is important to note that if we want to handle UI-related operations in callbacks, we need to ensure that callbacks are executed in the main thread.

At this point, there is a problem, if our notification is a two-level thread in the post, how can be in the main thread to deal with this notification? Or in other words, what if we want a notification post thread that is not the same thread as the forwarding thread? Let's see what the official documentation says:

For example, if an object running in a background thread was listening for notifications from the user interface, such as a Window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they is delivered on the default thread and redirect them to the AP Propriate thread.

In this case, "redirection" is where we capture notifications of these distributions in the default thread where notification is located, and then redirect them to the specified thread.

One way to implement redirection is to customize a notification queue (note that instead of Nsnotificationqueue objects, instead of an array), let this queue maintain the notification that we need to redirect. We are still to register a notice as usual observer, when notification come, first look at the post this notification thread is not the thread we expect, if not, then this notification stored in our queue, and send a signal (signal) to the desired thread to tell the thread that it needs to process a notification. After the specified thread receives the signal, it removes the notification from the queue and processes it.

The official documentation has given the sample code, which is borrowed here to test the actual results:

Code Listing 2: Post and forward a notification in different threads

@interface Viewcontroller () @property (nonatomic) Nsmutablearray *notifications;    Notification Queue @property (nonatomic) Nsthread *notificationthread;      Expected thread @property (nonatomic) Nslock *notificationlock;      Lock object used to lock the notification queue to avoid thread conflict @property (nonatomic) Nsmachport *notificationport;     The communication port used to send signals to the expected thread @end @implementation Viewcontroller-(void) viewdidload {[Super viewdidload];     NSLog (@ "Current thread =%@", [Nsthread CurrentThread]);    Initialize self.notifications = [[Nsmutablearray alloc] init];     Self.notificationlock = [[Nslock alloc] init];    Self.notificationthread = [Nsthread CurrentThread];    Self.notificationport = [[Nsmachport alloc] init];     Self.notificationPort.delegate = self; Add a port source to the current thread's run loop//when the Mach message arrives and the receive thread's run loop is not running, the kernel saves the message until the next time it enters the run loop [[Nsrunloop Currentrunloop] Addpor     T:self.notificationport Formode: (__bridge NSString *) kcfrunloopcommonmodes]; [[NsnotifIcationcenter Defaultcenter] addobserver:self selector: @selector (processnotification:) name:@ "Testnotification"     Object:nil]; Dispatch_async (Dispatch_get_global_queue (dispatch_queue_priority_default, 0), ^{[[Nsnotificationcenter DefaultCen     TER] postnotificationname:test_notification Object:nil Userinfo:nil]; });}     -(void) Handlemachmessage: (void *) msg {[Self.notificationlock lock];        while ([self.notifications Count]) {nsnotification *notification = [Self.notifications objectatindex:0];        [Self.notifications removeobjectatindex:0];        [Self.notificationlock unlock];        [Self processnotification:notification];    [Self.notificationlock Lock];     }; [Self.notificationlock unlock];}        -(void) Processnotification: (nsnotification *) Notification {if ([Nsthread currentthread]! = _notificationthread) {        Forward the notification to the correct thread.        [Self.notificationlock Lock]; [Self.notifications Addobject:notificatION];        [Self.notificationlock unlock];                                         [Self.notificationport sendbeforedate:[nsdate Date] Components:nil    From:nil reserved:0];        } else {//Process the notification here;        NSLog (@ "Current thread =%@", [Nsthread CurrentThread]);    NSLog (@ "process notification"); }} @end

after running, its output is as follows:

2015-03-11 23:38:31.637 test[1474:92483] Current thread = {Number = 1, name = main}2015-03-11 23:38:31.663 test[1474:92483 ] Current thread = {Number = 1, name = main}2015-03-11 23:38:31.663 test[1474:92483] Process notification

As you can see, the notification we have thrown in the global dispatch queue has been received in the main thread.

This way of implementation of the specific analysis and limitations you can refer to the official document delivering notifications to particular Threads, there is not much to explain. Of course, a better approach might be for us to subclass a nsnotificationcenter, or write a class alone to handle this kind of forwarding.

thread safety for Nsnotificationcenter

Apple's policy of notifying the hub to post and forward the same message in the same thread should be considered in terms of thread safety. Official documentation tells us that Nsnotificationcenter is a thread-safe class that allows us to use the same Nsnotificationcenter object in a multithreaded environment without locking. The text is in the Threading Programming Guide, specifically as follows:

The following classes and functions is generally considered to be thread-safe. You can use the same instance from multiple threads without first acquiring a lock. Nsarray ... Nsnotificationnsnotificationcenter ...

we can add/remove observers to any thread or post a notification on any thread.

Nsnotificationcenter has done a lot of work on thread safety, does that mean we can rest easy? Looking back at the first example, let's change it a little bit, 1.1 points:

common patterns for code listing 3:nsnotificationcenter

@interface observer:nsobject @end @implementation Observer-(instancetype) init{self    = [super init];     if (self)    {        _poster = [[Poster alloc] init];         [[Nsnotificationcenter Defaultcenter] addobserver:self selector: @selector (handlenotification:) name:test_ NOTIFICATION Object:nil]    }     return self;}-(void) Handlenotification: (nsnotification *) notification{    NSLog (@ "handle notification");} -(void) dealloc{    [[Nsnotificationcenter Defaultcenter] removeobserver:self];} @end//Other places [[Nsnotificationcenter Defaultcenter] Postnotificationname:test_notification Object:nil];

The code above is what we usually do: add a notification listener, define a callback, remove the listener when the owning object is released, and post a notification somewhere in the program. Simple and straightforward, if all this happens in a thread, or at least the Dealloc method is running in the-postnotificationname: thread (note: Nsnotification Post and forwarding are synchronous), then OK, There is no thread safety issue. But what happens if the Dealloc method and the-postnotificationname: method is not running in the same thread?

Let's change the code above:

Code Listing 4:nsnotificationcenter thread-safety issues raised

#pragma mark-poster @interface poster:nsobject @end @implementation Poster-(instancetype) init{self = [Super init     ];    if (self) {[Self Performselectorinbackground: @selector (postnotification) Withobject:nil]; } return self;} -(void) postnotification{[[Nsnotificationcenter Defaultcenter] postnotificationname:test_notification object:nil];}  @end #pragma mark-observer @interface observer:nsobject{Poster *_poster;} @property (Nonatomic, assign) Nsinteger I     @end @implementation Observer-(instancetype) init{self = [super init];         if (self) {_poster = [[Poster alloc] init]; [[Nsnotificationcenter Defaultcenter] addobserver:self selector: @selector (handlenotification:) name:test_    NOTIFICATION Object:nil]; } return self;}    -(void) Handlenotification: (nsnotification *) notification{NSLog (@ "Handle notification begin");    Sleep (1);     NSLog (@ "Handle notification end"); SELF.I = 10;} -(void) dealloc{[[NsnotificationCenter Defaultcenter] removeobserver:self]; NSLog (@ "Observer dealloc");}     @end #pragma mark-viewcontroller @implementation Viewcontroller-(void) viewdidload {[Super viewdidload]; __autoreleasing Observer *observer = [[Observer alloc] init];} @end
This code adds a TEST_NOTIFICATION notification listener to the main thread and removes it in the main thread, while our nsnotification is post in the background thread. In the notification handler, we let the thread where the callback is located sleeps for 1 seconds before setting the property I value. What will happen then? Let's start by looking at the output:
2015-03-14 00:31:41.286 sktest[932:88791] Handle notification begin2015-03-14 00:31:41.291 sktest[932:88713] Observer DEALLOC2015-03-14 00:31:42.361 sktest[932:88791] Handle notification end (LLDB)  //program throws "Thread self.i at 6:exc_ = 10" Bad_access (Code=exc_i386_gpflt) "

The classic memory error, the program crashes. In fact, from the output, we can see exactly what is going on. Let's briefly describe:

    1. When we register an observer, the notification Center holds a weak reference to the observer to ensure that the observer is available.

    2. The main thread calls the Dealloc operation to reduce the reference count of the Observer object to 0, when the object is freed.

    3. A background thread sends a notification, and if observer is not released at this point, it is used to go out of the message and execute the callback method. If the object is freed during callback execution, the above problem occurs.

Of course, the above example is intentional, but does not preclude a similar problem in the actual coding. Although Nsnotificationcenter is thread-safe, it does not mean that we can guarantee thread safety when we use it, and if we are not careful, we still have threading problems.

So what do we do? Here are some good suggestions:

    1. Try to handle notification-related operations in one thread, in most cases, to ensure that notifications are working correctly. However, we are not sure which thread to invoke the Dealloc method on, so this is still a bit more difficult.

    2. When registering for monitoring, use block-based APIs. This way we can continue to invoke the properties or methods of self on the block, which is handled by Weak-strong. Specifically, you can change the above code to try what effect.

    3. With objects with a safe life cycle, this object is a good example of a singleton object, and will not be released throughout the lifetime of the application.

    4. Use a proxy.

Summary

Nsnotificationcenter is thread-safe, but don't be misled by this fact. When it comes to multi-threading, we still need to be careful to avoid the threading problems that arise above. If you want to know more, you can view observers and Thread Safety.

Reference

    1. Notification Programming Topics

    2. Threading Programming Guide

    3. A few notes on nsnotification

    4. Nsnotificationcenter is Thread-safe not

    5. Observers and Thread Safety

Notification and multithreading

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.