我试图以线程安全的方式控制网络活动指标。
这是我目前正在做的方式,但我认为必须有更好的方法来做到这一点。我正在寻找使用锁,但它似乎是一个昂贵的操作。我一直在关注OSAtomicAdd,但在这种情况下无法弄清楚如何使用它。
+ (void)start
{
[self counterChange:1];
}
+ (void)stop
{
[self counterChange:-1];
}
+ (void)counterChange:(NSUInteger)change
{
static NSUInteger counter = 0;
static dispatch_queue_t queue;
if (!queue) {
queue = dispatch_queue_create("NetworkActivityIndicator Queue", NULL);
}
dispatch_sync(queue, ^{
if (counter + change <= 0) {
counter = 0;
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
} else {
counter += change;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
});
}
如何使用OSAtomicAdd完成这样的事情?
答案 0 :(得分:3)
您无法单独使用OSAtomicAdd
之类的内容来同步此类操作。需要锁定整个操作以确保其成功运行。
考虑this answer中建议的解决方案,基本上归结为:
static volatile int32_t NumberOfCallsToSetVisible = 0;
int32_t newValue = OSAtomicAdd32((setVisible ? +1 : -1), &NumberOfCallsToSetVisible);
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:(newValue > 0)];
如果从一个线程调用此代码,并将setVisible
设置为YES
,则对OSAtomicAdd32
的调用将向NumberOfCallsToSetVisible
添加1,从而导致{{1}被设置为1。
现在考虑如果该线程在执行下一行之前被抢占会发生什么,而另一个线程调用该函数并将newValue
设置为setVisible
。这次对NO
的调用将从OSAtomicAdd32
中减去1,从而导致NumberOfCallsToSetVisible
设置为0。
如果第二个线程继续,并且下一行被执行,newValue
不会大于零,那么将使用newValue
调用setNetworkActivityIndicatorVisible
方法。此时,活动指示器无论如何都不可见,所以这没有任何作用,但它也没有造成任何伤害。
但是,最终我们将切换回NO
设置为1的第一个线程。因此,当该线程执行下一行时,newValue
显然大于零,并使用newValue
调用setNetworkActivityIndicatorVisible
方法,使活动指示器可见。
因此,我们在将YES
设置为setVisible
时将该函数调用一次,并将YES
设置为setVisible
再次调用该函数。您可能会认为这会导致活动指示器不可见,但事实并非如此。事实上,如果没有其他电话,它将永远保持可见。这显然是不对的。
最重要的是,您需要将整个事物包裹在NO
块或类似的内容中。
答案 1 :(得分:1)
我建议您使用同一系列函数中的OSAtomicAdd32
而不是OSAtomicCompareAndSwap32
。
+ (void)counterChange:(NSUInteger)change
{
static int32_t counter = 0;
int32_t localCounter, newCounter;
do
{
localCounter = counter;
newCounter = localCounter + change;
newCounter = newCounter <= 0 ? 0 : newCounter;
} while (!OSAtomicCompareAndSwap32(localCounter, newCounter, &counter));
[UIApplication sharedApplication].networkActivityIndicatorVisible = counter > 0;
}
该函数会将localCounter
与counter
的当前值进行比较,只有当它们匹配时才会将counter
更改为newCounter
,所有内容都会自动更改。如果某个其他线程在当前线程counter
和localCounter
调用之间发生OSAtomicCompareAndSwap32
更改,则检查将失败,并且将重试。
即使看起来它可能会永远留下一些线程循环,但这种结构在现实条件下足够安全。
答案 2 :(得分:0)
NSLock(适用于iOS 2.0+和OS X 10.0+)是您正在寻找的。 p>
NSLock对象用于协调同一应用程序中多个执行线程的操作。 NSLock对象可用于调解对应用程序的全局数据的访问,或保护代码的关键部分,允许它以原子方式运行。
您可以初始化应用代理中的锁定,并围绕计数器代码调用-lock
和-unlock
:
// Assuming the application delegate implements -counterChangeLock
// to return a momoized instance of NSLock
+ (NSLock *)counterChangeLock
{
return [(AppDelegate *)([UIApplication sharedApplication].delegate) counterChangeLock];
}
+ (void)start
{
[[self counterChangeLock] lock]; // blocks if counter is locked already
// safely increment counter
[[self counterChangeLock] unlock];
}
+ (void)stop
{
[[self counterChangeLock] lock];
// safely decrement counter
[[self counterChangeLock] unlock];
}
答案 3 :(得分:0)
我不确定为什么所有这些原子操作在使问题复杂化时都会被使用,并且在解释UIApplication时我们需要修复线程同步这一事实并不能解决问题。
使用@synchronized的建议是正确的解决方案,因为它为您提供了一个关于递增和调用UIApplication的互斥锁。如果您对@synchronized进行基准测试,那么您会看到它的速度非常快,而且这种情况非常罕见,原子变量和比较和交换容易出错且不必要。不这样做的唯一原因是if(self)是否在许多其他部分同步,在这种情况下你可以为此目的保留一个NSObject或使用NSLock&amp;等同物。
因此:
+ (void) incrementActivityCounter {
[self changeActivityCounter:1];
}
+ (void) decrementActivityCounter {
[self changeActivityCounter:-1];
}
+ (void) changeActivityCounter:(int)change {
static int counter = 0;
@synchronized(self) {
counter += change;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:counter > 0];
}
}
答案 4 :(得分:0)
UIApplication的networkActivityIndicatorVisible是一个非原子属性,所以它只能在主线程中使用。因此,不需要同步计数器,因为它永远不应该从线程调用。
是一个简单的静态int和停止时开始和减少的增量。