2

我试图以线程安全的方式控制网络活动指示器。

这是我目前正在做的方式,但我认为必须有更好的方法来做到这一点。我一直在寻找使用锁,但这似乎是一项昂贵的操作。我一直在研究 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 完成这样的事情?

4

5 回答 5

3

你不能依靠像OSAtomicAdd单独这样的东西来同步这种操作。需要锁定整个操作以确保其成功运行。

考虑这个答案中建议的解决方案,基本上可以归结为:

static volatile int32_t NumberOfCallsToSetVisible = 0;
int32_t newValue = OSAtomicAdd32((setVisible ? +1 : -1), &NumberOfCallsToSetVisible);
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:(newValue > 0)];

如果从一个线程调用此代码,并setVisible设置为YES,则调用OSAtomicAdd32将加 1 以NumberOfCallsToSetVisible导致newValue设置为 1。

现在考虑如果该线程在执行下一行之前被抢占,并且另一个线程使用setVisibleset调用该函数会发生什么NO。这次调用OSAtomicAdd32将减去 1,NumberOfCallsToSetVisible导致newValue设置为 0。

如果第二个线程继续,并且执行了下一行,newValue则不大于零,因此setNetworkActivityIndicatorVisible将使用 调用该方法NO。此时活动指示器仍然不可见,所以这没有任何作用,但也不会造成任何伤害。

但是,最终我们将切换回newValue设置为 1 的第一个线程。因此,当该线程执行下一行时,newValue显然大于零,并且setNetworkActivityIndicatorVisible将调用该方法YES,使活动指示器可见。

所以我们用 set to 调用了这个函数一次,用setVisibleset to再YES调用一次。您会期望这会导致活动指示器不可见,但事实并非如此。事实上,如果没有其他调用,它将永远保持可见。这显然是不对的。setVisibleNO

底线是您将需要将整个东西包装在一个@synchronize块或类似的东西中。

于 2013-06-18T19:57:24.787 回答
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,并且只有当它们匹配时才会更改counternewCounter,所有这些都是原子的。counter如果在当前线程接受localCounter和调用之间有其他线程发生变化OSAtomicCompareAndSwap32,则检查将失败,并且将重试。

即使看起来它可能会让一些线程永远循环,但这种结构在现实世界条件下是足够安全的。

于 2013-06-18T20:19:29.757 回答
0

我不确定为什么要使用所有这些原子操作,因为它使问题复杂化并且不能解决我们需要修复线程同步的事实,即在告诉 UIApplication 我们需要的数字时。

使用@synchronized 的建议是正确的解决方案,因为它为您提供了一个围绕递增和调用 UIApplication 的互斥锁。如果您对 @synchronized 进行基准测试,您会发现它的速度非常快,而且这种事情非常罕见,以至于原子变量 & 比较和交换容易出错且不必要。不这样做的唯一原因是,如果 (self) 在许多其他部分同步,在这种情况下,您可以为此目的保留一个 NSObject 或使用 NSLock 和等效项。

因此:

+ (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];
    }
}
于 2015-01-21T00:28:02.147 回答
0

NSLock(适用于 iOS 2.0+ 和 OS X 10.0+)是您正在寻找的。

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];
}
于 2013-10-08T18:07:28.793 回答
0

UIApplication 的 networkActivityIndi​​catorVisible 是一个非原子属性,因此只能在主线程中使用。因此不需要同步计数器,因为它不应该从线程中调用。只需要一个简单的静态 int 和开始时的增量和停止时的减量。

于 2015-08-28T22:48:18.133 回答