40

我在我的應用程序中有一個場景,我想在一個方法中執行一些耗時的任務,包括一些數據處理以及UI更新。我的方法是這樣的,dispatch_async(dispatch_get_main_queue(),^ {...});等到完成了?

- (void)doCalculationsAndUpdateUIs { 

    // DATA PROCESSING 1 
    // UI UPDATE 1 

    // DATA PROCESSING 2 
    // UI UPDATE 2 

    // DATA PROCESSING 3 
    // UI UPDATE 3 
} 

由於它是費時,我想這樣做在後臺線程的數據處理,使用,

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{ 

但作爲兩個數據處理和UI更新是在同樣的方法,我想用移動僅在主線程的UI更新,

dispatch_async(dispatch_get_main_queue(), ^{ 

最後我的方法是這樣的,

- (void)doCalculationsAndUpdateUIs { 

    // DATA PROCESSING 1 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     // UI UPDATE 1 
    }); 

    /* I expect the control to come here after UI UPDATE 1 */ 

    // DATA PROCESSING 2 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     // UI UPDATE 2 
    }); 

    /* I expect the control to come here after UI UPDATE 2 */ 

    // DATA PROCESSING 3 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     // UI UPDATE 3 
    }); 
} 

這是否真的有用?這真的是一個好習慣嗎?達到此目的的最佳方法是什麼?

P.S.所有這三項行動都是相互關聯的。


編輯:對不起球員。我有在上面的代碼中錯過了一行。我的實際代碼看起來像這樣。

- (void)doCalculationsAndUpdateUIs { 

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

     // DATA PROCESSING 1 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      // UI UPDATE 1 
     }); 

     /* I expect the control to come here after UI UPDATE 1 */ 

     // DATA PROCESSING 2 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      // UI UPDATE 2 
     }); 

     /* I expect the control to come here after UI UPDATE 2 */ 

     // DATA PROCESSING 3 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      // UI UPDATE 3 
     }); 
    }); 
} 

再一次,我真的爲混亂道歉。

+1

也許'dispatch_sync'可幫助這裏... –

+0

@DaveDeLong也許。我已經看到開發人員使用'dispatch_sync',因爲它看起來合乎邏輯,但實際上往往不需要。 EmptyStack沒有分享他想等的原因,所以我們不可能告知這是不是一個好主意。 – Rob

+1

@Rob yep,'dispatch_sync'可能非常危險,因爲這樣更容易死鎖。但有時*這是正確的做法。 :) –

回答

94

不,它不會等待,您在該樣本中的做法並不是很好的做法。

dispatch_async總是異步。只是你將所有UI塊排入同一隊列,所以不同的塊將按順序運行,但與你的數據處理代碼並行。

如果您希望更新等待,您可以改爲使用dispatch_sync

// This will wait to finish 
dispatch_sync(dispatch_get_main_queue(), ^{ 
    // Update the UI on the main thread. 
}); 

另一種方法是嵌套排隊塊。我不會雖然推薦它的多個層次。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
    // Background work 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     // Update UI 

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
      // Background work 

      dispatch_async(dispatch_get_main_queue(), ^{ 
       // Update UI 
      }); 
     }); 
    }); 
}); 

如果您需要更新UI以等待,那麼您應該使用同步版本。有一個後臺線程等待主線程是相當好的。 UI更新應該非常快。

+0

很酷。感謝您的解釋。如果我將所有** dispatch_async(dispatch_get_main_queue()**)更改爲** dispatch_sync(dispatch_get_main_queue()**? – EmptyStack

+3

是的,那麼它會正常工作,這將使它們同步(等待)。 –

+0

非常感謝。 – EmptyStack

1

不,它不會等。您可以使用performSelectorOnMainThread:withObject:waitUntilDone:

+0

Cool ..謝謝.. – EmptyStack

+0

但是對於像UI更新這樣的東西,你不需要等待,如果你想等待,只需要向後臺線程添加一個調度作爲最後一個您發送到主線程的代碼語句。 – gnasher729

+0

這是GCD的東西。所以編寫調度更清潔,更好。 – Karsten

10

您必須將主隊列分派到運行計算的塊中。例如(在這裏我創建了一個調度隊列,不使用一個全球性):

dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL); 
dispatch_async(queue, ^{ 
    // Do some computation here. 

    // Update UI after computation. 
    dispatch_async(dispatch_get_main_queue(), ^{ 
    // Update the UI on the main thread. 
    }); 
}); 

當然,如果你創建隊列不要忘記dispatch_release如果你前6.0瞄準的是iOS版本。

+0

很酷。感謝您的回答! – EmptyStack

8

您提出的doCalculationsAndUpdateUIs會執行數據處理並將UI更新調度到主隊列。我認爲當你第一次調用它時,你已經發送了doCalculationsAndUpdateUIs到後臺隊列。

雖然在技術上很好,但有點脆弱,這取決於您每次調用它時都會記住將其分發到背景:我會建議您將其調度到後臺並分派回主隊列從相同的方法中,因爲它使邏輯明確的和更強大的等

因此,它可能看起來像:

- (void)doCalculationsAndUpdateUIs { 

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{ 

     // DATA PROCESSING 1 

     dispatch_async(dispatch_get_main_queue(), ^{ 
      // UI UPDATION 1 
     }); 

     /* I expect the control to come here after UI UPDATION 1 */ 

     // DATA PROCESSING 2 

     dispatch_async(dispatch_get_main_queue(), ^{ 
      // UI UPDATION 2 
     }); 

     /* I expect the control to come here after UI UPDATION 2 */ 

     // DATA PROCESSING 3 

     dispatch_async(dispatch_get_main_queue(), ^{ 
      // UI UPDATION 3 
     }); 
    }); 
} 

在是否你dispatch_async異步派遣你的UI更新條款(其中後臺進程將不是等待UI更新)或與dispatch_sync同步(其中它等待UI更新),問題是爲什麼要同步執行:您是否真的想放慢後臺進程?等待UI更新,或者您希望後臺進程在UI更新發生時繼續進行。

通常情況下,您會像在原始問題中一樣使用dispatch_async異步調度UI更新。是的,在某些特殊情況下需要同步分派代碼(例如,通過在主隊列上執行所有更新來同步某些類屬性的更新),但通常會派發UI更新異步進行。同步分派代碼可能會導致問題(如死鎖),所以我的一般建議是,如果有一些令人信服的需要,你可能應該同步調度UI更新,否則你應該設計你的解決方案,以便你可以異步分派它們。


在回答你的問題,這是否是「實現這一目標的最佳方式」,這是我們很難不知道更多關於正在解決的業務問題再說。例如,如果你可能多次調用這個doCalculationsAndUpdateUIs,我可能會傾向於使用自己的串行隊列而不是併發的全局隊列,以確保它們不會互相超越。或者,如果您可能需要在用戶解散場景或再次調用該方法時取消此doCalculationsAndUpdateUIs的功能,那麼我可能傾向於使用提供取消功能的操作隊列。這完全取決於你想要達到的目標。

但是,一般來說,異步調度複雜任務到後臺隊列,然後異步調度UI更新回主隊列的模式非常普遍。

+0

糟糕。其實我原來的代碼看起來像你上面發佈的。我錯過了在我的問題中發佈。抱歉。我更新了我的問題。感謝您的偉大建議。 – EmptyStack

0

OK,有這樣做的方法有兩種:

// GLOBAL_CONCURRENT_QUEUE 


- (void)doCalculationsAndUpdateUIsWith_GlobalQUEUE 
{ 
    dispatch_queue_t globalConcurrentQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    dispatch_async(globalConcurrentQ, ^{ 

     // DATA PROCESSING 1 
     sleep(1); 
     NSLog(@"Hello world chekpoint 1"); 
     dispatch_sync(dispatch_get_main_queue(), ^{ 
      // UI UPDATION 1 
      sleep(1); 
      NSLog(@"Hello world chekpoint 2"); 
     }); 

     /* the control to come here after UI UPDATION 1 */ 
     sleep(1); 
     NSLog(@"Hello world chekpoint 3"); 
     // DATA PROCESSING 2 

     dispatch_sync(dispatch_get_main_queue(), ^{ 
      // UI UPDATION 2 
      sleep(1); 
      NSLog(@"Hello world chekpoint 4"); 
     }); 

     /* the control to come here after UI UPDATION 2 */ 
     sleep(1); 
     NSLog(@"Hello world chekpoint 5"); 
     // DATA PROCESSING 3 

     dispatch_sync(dispatch_get_main_queue(), ^{ 
      // UI UPDATION 3 
      sleep(1); 
      NSLog(@"Hello world chekpoint 6"); 
     }); 
    }); 
} 



// SERIAL QUEUE 
- (void)doCalculationsAndUpdateUIsWith_GlobalQUEUE 
{ 

    dispatch_queue_t serialQ = dispatch_queue_create("com.example.MyQueue", NULL); 
    dispatch_async(serialQ, ^{ 

     // DATA PROCESSING 1 
     sleep(1); 
     NSLog(@"Hello world chekpoint 1"); 

     dispatch_sync(dispatch_get_main_queue(), ^{ 
      // UI UPDATION 1 
      sleep(1); 
      NSLog(@"Hello world chekpoint 2"); 
     }); 


     sleep(1); 
     NSLog(@"Hello world chekpoint 3"); 
     // DATA PROCESSING 2 

     dispatch_sync(dispatch_get_main_queue(), ^{ 
      // UI UPDATION 2 
      sleep(1); 
      NSLog(@"Hello world chekpoint 4"); 
     }); 
    }); 
} 
+0

這不是世界背景..你可以給我,可以在後臺工作 –

+0

我不明白你,只是從主線刪除睡眠。 – kokemomuke

2

如果你想運行一個獨立的排隊操作和你不關心其他併發操作,您可以使用全局併發隊列:

dispatch_queue_t globalConcurrentQueue = 
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 

這將返回併發隊列,如文檔中列出的優先考慮:

DISPATCH_QUEUE_PRIORITY_HIGH派發到隊列的項目將以高優先級運行,即隊列將被安排在任何默認優先級或低優先級隊列之前執行。

DISPATCH_QUEUE_PRIORITY_DEFAULT調度到隊列的項目將以默認優先級運行,即在調度了所有高優先級隊列之後,但在調度了任何低優先級隊列之前,該隊列將被調度執行。

DISPATCH_QUEUE_PRIORITY_LOW派送到隊列的項目將以低優先級運行,即在排定了所有默認優先級和高優先級隊列之後,將調度隊列以執行。

DISPATCH_QUEUE_PRIORITY_BACKGROUND調度到隊列的項目將以後臺優先級運行,也就是說,在調度了所有更高優先級隊列後,系統將按照setpriority在具有後臺狀態的線程上運行此隊列上的項目(2)(即磁盤I/O受到限制並且線程的調度優先級設置爲最低值)。

1
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL); 
dispatch_async(queue, ^{ 
    // Do some computation here. 

    // Update UI after computation. 
    dispatch_async(dispatch_get_main_queue(), ^{ 
    // Update the UI on the main thread. 
    }); 
});