鴻蒙應用開發之執行緒
1、概述
如果應用的業務邏輯比較複雜,可能需要建立多個執行緒來執行多個任務。這種情況下,程式碼複雜難以維護,任務與執行緒的互動也會更加繁雜。要解決此問題,開發者可以使用“
TaskDispatcher
”來分發不同的任務。
在啟動應用時,系統會為該應用建立一個稱為“主執行緒”的執行執行緒。該執行緒隨著應用建立或消失,是應用的核心執行緒。
UI
介面的顯示和更新等操作,都是在主執行緒上進行。主執行緒又稱UI
執行緒,預設情況下,所有的操作都是在主執行緒上執行。
- 多執行緒可用在需要處理長時間等待的任務中,例如網路訪問和資料庫訪問等。
- 在
Harmonyos
中,TaskDispatcher
任務分發器可用來分發不同的任務。 - 任務的優先順序分為
HIGH
、DEFAULT
和LOW
。
優先順序 | 詳細描述 |
---|---|
HIGH | 最高任務優先順序,比預設優先順序、低優先順序的任務有更高的機率得到執行。 |
DEFAULT | 預設任務優先順序, 比低優先順序的任務有更高的機率得到執行。 |
LOW | 低任務優先順序,比高優先順序、預設優先順序的任務有更低的機率得到執行。 |
2、任務分發器
TaskDispatcher
是一個任務分發器,它是Ability
分發任務的基本介面,隱藏任務所線上程的實現細節
2.1、TastDispatcher介面類的四種實現
TaskDispatcher
具有多種實現,每種實現對應不同的任務分發器。在分發任務時可以指定任務的優先順序,由同一個任務分發器分發出的任務具有相同的優先順序。系統提供的任務分發器有GlobalTaskDispatcher
、ParallelTaskDispatcher
、SerialTaskDispatcher
、SpecTaskDispatcher
。
2.1.1、GlobalTaskDispatcher全域性併發任務分發器
全域性併發任務分發器,由
Ability
執行getGlobalTaskDispatcher()
獲取。適用於任務之間沒有聯絡的情況。一個應用只有一個GlobalTaskDispatcher
,它在程式結束時才被銷燬。
2.1.2、ParallelTaskDispatcher併發任務分發器
併發任務分發器,由
Ability
執行createParallelTaskDispatcher()
建立並返回。與GlobalTaskDispatcher
不同的是,ParallelTaskDispatcher
不具有全域性唯一性,可以建立多個。開發者在建立或銷燬dispatcher
時,需要持有對應的物件引用 。
2.1.3、SerialTaskDispatcher序列任務分發器
序列任務分發器,由
Ability
執行createSerialTaskDispatcher()
建立並返回。由該分發器分發的所有的任務都是按順序執行,但是執行這些任務的執行緒並不是固定的。如果要執行並行任務,應使用ParallelTaskDispatcher
或者GlobalTaskDispatcher
,而不是建立多個SerialTaskDispatcher
。如果任務之間沒有依賴,應使用GlobalTaskDispatcher
來實現。它的建立和銷燬由開發者自己管理,開發者在使用期間需要持有該物件引用。
2.1.4、SpecTaskDispatcher專有任務分發器
專有任務分發器,繫結到專有執行緒上的任務分發器。目前已有的專有執行緒是主執行緒。
UITaskDispatcher
和MainTaskDispatcher
都屬於SpecTaskDispatcher
。建議使用UITaskDispatcher
。
UITaskDispatcher
:繫結到應用主執行緒的專有任務分發器, 由Ability
執行getUITaskDispatcher()
建立並返回。 由該分發器分發的所有的任務都是在主執行緒上按順序執行,它在應用程式結束時被銷燬
2.2、四種任務分發器的獲取
//1、獲取全域性併發任務分發器,一個應用程式只有一個
TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(null);
//2、獲取併發任務分發器,可以建立多個,建立的時候,傳入的第一個引數為一個字串,表示一個併發任務分發器的引用,在銷燬的時候需要指定對應的引用來銷燬指定的分發器。
TaskDispatcher parallelTaskDispatcher = createParallelTaskDispatcher("parallelTaskDispatcher", null);
//3、獲取序列任務分發器,類似有序列表。
TaskDispatcher serialTaskDispatcher = createSerialTaskDispatcher("serialTaskDispatcher",null);
//4、獲取專有任務分發器
//4、1.獲取主執行緒任務分發器
TaskDispatcher mainTaskDispatcher = getMainTaskDispatcher();
//4、2.獲取UI執行緒任務分發器
TaskDispatcher uiTaskDispatcher = getUITaskDispatcher();
2.3、八個派發任務的API應用
2.3.1、syncDispatch同步派發任務
派發任務並在當前執行緒等待任務執行完成。在返回前,當前執行緒會被阻塞。
/**
* @description 同步派發任務,派發任務並在當前執行緒等待任務執行完成。在返回前,當前執行緒會被阻塞。
* @author PengHuAnZhi
* @date 2021/1/13 13:46
*/
private void syncDispatch() {
globalTaskDispatcher.syncDispatch(() -> HiLog.info(label, "同步任務1執行"));
HiLog.info(label, "同步任務1執行完畢");
globalTaskDispatcher.syncDispatch(() -> HiLog.info(label, "同步任務2執行"));
HiLog.info(label, "同步任務2執行完畢");
globalTaskDispatcher.syncDispatch(() -> HiLog.info(label, "同步任務3執行"));
HiLog.info(label, "同步任務3執行完畢");
}
同步任務嚴格按照任務分配順序執行
如果對
syncDispatch
使用不當, 將會導致死鎖。如下情形可能導致死鎖發生:
- 在專有執行緒上,利用該專有任務分發器進行
syncDispatch
。 - 在被某個序列任務分發器(
dispatcher_a
)派發的任務中,再次利用同一個序列任務分發器(dispatcher_a
)物件派發任務。 - 在被某個序列任務分發器(
dispatcher_a
)派發的任務中,經過數次派發任務,最終又利用該(dispatcher_a
)序列任務分發器派發任務。例如:dispatcher_a
派發的任務使用dispatcher_b
進行任務的派發,在dispatcher_b
派發的任務中又利用dispatcher_a
進行派發任務。 - 序列任務分發器(
dispatcher_a
)派發的任務中利用序列任務分發器(dispatcher_b
)進行同步派發任務,同時dispatcher_b
派發的任務中利用序列任務分發器(dispatcher_a
)進行同步派發任務。在特定的執行緒執行順序下將導致死鎖。
2.3.2、asyncDispatch非同步派發任務
派發任務,並立即返回,返回值是一個可用於取消任務的介面。
/**
* @description 非同步派發任務,派發任務,並立即返回,返回值是一個可用於取消任務的介面。
* @author PengHuAnZhi
* @date 2021/1/13 13:47
*/
private void asyncDispatch() {
Revocable revocable1 = globalTaskDispatcher.asyncDispatch(() -> HiLog.info(label, "非同步任務1執行"));
Revocable revocable2 = globalTaskDispatcher.asyncDispatch(() -> HiLog.info(label, "非同步任務2執行"));
HiLog.info(label, "外部程式碼執行");
}
非同步任務的執行順序不固定,是隨機的
由於返回值為一個
Revocable
,此介面可用於撤銷任務分發,嘗試一下
/**
* @description 非同步派發任務,派發任務,並立即返回,返回值是一個可用於取消任務的介面。
* @author PengHuAnZhi
* @date 2021/1/13 13:47
*/
private void asyncDispatch() {
Revocable revocable1 = globalTaskDispatcher.asyncDispatch(() -> HiLog.info(label, "非同步任務1執行"));
Revocable revocable2 = globalTaskDispatcher.asyncDispatch(() -> HiLog.info(label, "非同步任務2執行"));
revocable2.revoke();
HiLog.info(label, "外部程式碼執行");
}
2.3.3、delayDispatch非同步延遲派發任務
非同步執行,函式立即返回,內部會在延時指定時間後將任務派發到相應佇列中。延時時間引數僅代表在這段時間以後任務分發器會將任務加入到佇列中,任務的實際執行時間可能晚於這個時間。具體比這個數值晚多久,取決於佇列及內部執行緒池的繁忙情況。
/**
* @description 非同步延遲派發任務:非同步執行,函式立即返回,內部會在延時指定時間後將任務派發到相應佇列中。
* 延時時間引數僅代表在這段時間以後任務分發器會將任務加入到佇列中,任務的實際執行時間可能
* 晚於這個時間。具體比這個數值晚多久,取決於佇列及內部執行緒池的繁忙情況。
* @author PengHuAnZhi
* @date 2021/1/13 13:48
*/
private void delayDispatch() {
//當前時間
final long callTime = System.currentTimeMillis();
//延遲時間
final long delayTime = 50;
Revocable revocable = globalTaskDispatcher.delayDispatch(() -> {
HiLog.info(label, "非同步延遲任務1開始執行");
//獲取當前任務執行時間和開始派發任務的時間差
final long actualDelayMs = System.currentTimeMillis() - callTime;
HiLog.info(label, "任務實際延遲執行時間比理論延遲執行時間晚:" + (actualDelayMs - delayTime) + "ms");
}, delayTime);
HiLog.info(label, "外部程式碼執行");
}
2.3.4、group任務組
表示一組任務,且該組任務之間有一定的聯絡,由
TaskDispatcher
執行createDispatchGroup
建立並返回。將任務加入任務組,返回一個用於取消任務的介面。
/**
* @description 任務組:表示一組任務,且該組任務之間有一定的聯絡,由 TaskDispatcher 執行 createDispatchGroup
* 建立並返回。將任務加入任務組,返回一個用於取消任務的介面。
* @author PengHuAnZhi
* @date 2021/1/13 13:48
*/
private void group() {
//建立任務組
Group group = parallelTaskDispatcher.createDispatchGroup();
// 將任務 1 加入任務組,
parallelTaskDispatcher.asyncGroupDispatch(group, () -> HiLog.info(label, "任務1加入任務組"));
// 將任務 2 加入任務組,
parallelTaskDispatcher.asyncGroupDispatch(group, () -> HiLog.info(label, "任務2加入任務組"));
// 在任務組中的所有任務執行完成後執行指定任務。
parallelTaskDispatcher.groupDispatchNotify(group, () -> HiLog.info(label, "任務組執行完畢"));
}
可以看到非同步任務組中的任務順序不是固定的,而任務組後置任務是肯定在任務組所有任務都執行完畢再執行的
2.3.5、revocable取消任務
Revocable
是取消一個非同步任務的介面。非同步任務包括通過asyncDispatch
、delayDispatch
、asyncGroupDispatch
派發的任務。如果任務已經在執行中或執行完成,則會返回取消失敗。
/**
* @description 取消任務:Revocable 是取消一個非同步任務的介面。非同步任務包括通過 asyncDispatch、delayDispatch、asyncGroupDispatch 派發的任務。如果任務已經在執行中或執行完成,則會返回取消失敗。
* @author PengHuAnZhi
* @date 2021/1/13 13:48
*/
private void revocable() {
Revocable revocable1 = globalTaskDispatcher.asyncDispatch(() -> HiLog.info(label, "任務1執行了"));
Revocable revocable2 = globalTaskDispatcher.delayDispatch(() -> HiLog.info(label, "任務2執行了"), 10);
boolean revoke1 = revocable1.revoke();
boolean revoke2= revocable2.revoke();
HiLog.info(label, "任務1是否撤銷:"+ revoke1);
HiLog.info(label, "任務2是否撤銷:"+ revoke2);
}
2.3.6、syncDispatchBarrier同步設定屏障任務
在任務組上設立任務執行屏障,同步等待任務組中的所有任務執行完成,再執行指定任務。在全域性併發任務分發器(
GlobalTaskDispatcher
)上同步設定任務屏障,將不會起到屏障作用。
/**
* @description 同步設定屏障任務:在任務組上設立任務執行屏障,同步等待任務組中的所有任務執行完成,再執行指定任務。
* @author PengHuAnZhi
* @date 2021/1/13 13:49
*/
private void syncDispatchBarrier( ) {
// 建立任務組。
Group group = parallelTaskDispatcher.createDispatchGroup();
// 將任務加入任務組,返回一個用於取消任務的介面。
parallelTaskDispatcher.asyncGroupDispatch(group, () -> {
HiLog.info(label, "非同步任務1執行");
});
parallelTaskDispatcher.asyncGroupDispatch(group, () -> {
HiLog.info(label, "非同步任務2執行");
});
parallelTaskDispatcher.syncDispatchBarrier(() -> {
HiLog.info(label, "任務組執行完畢,執行後置同步任務");
});
HiLog.info(label, "任務組執行完畢,後置同步任務執行完畢");
}
同步設定屏障任務,任務組中無論是同步還是非同步,後置同步任務都是在任務組執行完畢後再執行
2.3.7、asyncDispatchBarrier非同步設定屏障任務
在任務組上設立任務執行屏障後直接返回,指定任務將在任務組中的所有任務執行完成後再執行。在全域性併發任務分發器(
GlobalTaskDispatcher
)上非同步設定任務屏障,將不會起到屏障作用。可以使用併發任務分發器(ParallelTaskDispatcher
)分離不同的任務組,達到微觀並行、巨集觀序列的行為。
/**
* @description 非同步設定屏障任務:在任務組上設立任務執行屏障後直接返回,指定任務將在任務組中的所有任務執行完成後再執行。
* @author PengHuAnZhi
* @date 2021/1/13 13:49
*/
private void asyncDispatchBarrier() {
// 建立任務組。
Group group = parallelTaskDispatcher.createDispatchGroup();
// 將任務加入任務組,返回一個用於取消任務的介面。
parallelTaskDispatcher.asyncGroupDispatch(group, () -> {
HiLog.info(label, "非同步任務1執行");
});
parallelTaskDispatcher.asyncGroupDispatch(group, () -> {
HiLog.info(label, "非同步任務2執行");
});
parallelTaskDispatcher.asyncDispatchBarrier(() -> {
HiLog.info(label, "後置非同步任務執行");
});
HiLog.info(label, "外部任務執行");
}
非同步設定屏障任務,後置非同步任務始終在任務組所有任務執行完畢後再執行,其他任何任務的執行順序對他來說都不關心,如果它後面還有其他任務,是有可能在它前面執行的
2.3.8、applyDispatch執行多次任務
對指定任務執行多次。
/**
* @description 執行多次任務:對指定任務執行多次。
* @author PengHuAnZhi
* @date 2021/1/13 13:50
*/
private void applyDispatch() {
//總執行次數
final int total = 10;
//倒數計時器
final CountDownLatch latch = new CountDownLatch(total);
//長整形列表陣列
final ArrayList<Long> indexList = new ArrayList<>(total);
//執行多次任務
parallelTaskDispatcher.applyDispatch((index) -> {
indexList.add(index);
latch.countDown();
HiLog.info(label, "" + index);
}, total);
//設定任務超時
try {
//使當前執行緒等待,直到倒計時計數到0為止。
latch.await();
} catch (InterruptedException e) {
HiLog.info(label, "任務超時!");
}
HiLog.info(label, "執行了" + indexList.size() + "次");
}
3、執行緒間通訊
3.1、概念
在開發過程中,開發者經常需要在當前執行緒中處理下載任務等較為耗時的操作,但是又不希望當前的執行緒受到阻塞。此時,就可以使用
EventHandler
機制。EventHandler
是HarmonyOS
用於處理執行緒間通訊的一種機制,可以通過EventRunner
建立新執行緒,將耗時的操作放到新執行緒上執行。這樣既不阻塞原來的執行緒,任務又可以得到合理的處理。比如:主執行緒使用EventHandler
建立子執行緒,子執行緒做耗時的下載圖片操作,下載完成後,子執行緒通過EventHandler
通知主執行緒,主執行緒再更新UI
。(看完描述我覺得這和Android
裡面的Handler
類似啊)
3.2、三種執行緒通訊方式
3.2.1、投遞 InnerEvent 事件
myEventHandler1 = new EventHandler(runnerA) {
@Override
protected void processEvent(InnerEvent event) {
super.processEvent(event);
if (event == null) {
return;
}
int eventId = event.eventId;
switch (eventId) {
case 1: {
HiLog.info(hiLogLabel, "1");
break;
}
case 2: {
HiLog.info(hiLogLabel, "2");
break;
}
case 3: {
HiLog.info(hiLogLabel, "3");
break;
}
default:
break;
}
}
};
/**
* EventHandler 投遞 InnerEvent 事件
*/
private void initInnerEvent() {
// 向執行緒A傳送事件
long param = 0;
Object object = EventRunner.current();
InnerEvent event = InnerEvent.get(1, param, object);
myEventHandler1.sendEvent(event);
runnerA.run();
runnerA.stop();
}
3.2.2、投遞 Runnable 任務
myEventHandler2 = new EventHandler(runnerB) {
@Override
protected void processEvent(InnerEvent event) {
super.processEvent(event);
}
};
/**
* EventHandler 投遞 Runnable 任務
*/
private void initRunnable() {
Runnable task1 = () -> HiLog.info(hiLogLabel, "Runnable1執行");
Runnable task2 = () -> HiLog.info(hiLogLabel, "Runnable2執行");
myEventHandler2.postTask(task2, 0, EventHandler.Priority.IMMEDIATE);
myEventHandler2.postTask(task1, 0, EventHandler.Priority.IMMEDIATE);
runnerB.run();
runnerB.stop();
}
3.2.3、在新建立的執行緒裡投遞事件到原執行緒
myEventHandler3 = new EventHandler(runnerC) {
@Override
protected void processEvent(InnerEvent event) {
super.processEvent(event);
if (event == null) {
return;
}
int eventId = event.eventId;
Object object = event.object;
switch (eventId) {
case 1:
// 待執行的操作,由開發者定義
break;
case 2:
// 將原先執行緒的EventRunner例項投遞給新建立的執行緒
if (object instanceof EventRunner) {
EventRunner runner2 = (EventRunner) object;
}
// 將原先執行緒的EventRunner例項與新建立的執行緒的EventHandler繫結
EventHandler myHandler = new EventHandler(runnerC) {
@Override
public void processEvent(InnerEvent event) {
//需要在原先執行緒執行的操作
HiLog.info(hiLogLabel,"原執行緒操作");
}
};
InnerEvent event2 = InnerEvent.get(1, 0, null);
myHandler.sendEvent(event2); // 投遞事件到原先的執行緒
break;
default:
break;
}
}
};
/**
* 在新建立的執行緒裡投遞事件到原執行緒
*/
private void initNewToOld() {
int eventId = 2;
long param = 0;
Object object = EventRunner.current();
InnerEvent event = InnerEvent.get(eventId, param, object);
myEventHandler3.sendEvent(event);
runnerC.run();
runnerC.stop();
}
3.2.4、完整程式碼
/**
* @description 糊里糊塗的執行緒通訊
* @author PengHuAnZhi
* @date 2021/1/13 20:46
*/
public class MainAbilitySlice extends AbilitySlice {
private static HiLogLabel hiLogLabel = new HiLogLabel(3, 1, "MainAbilitySlice");
private EventRunner runnerA;
private EventRunner runnerB;
private EventRunner runnerC;
private EventHandler myEventHandler1;
private EventHandler myEventHandler2;
private EventHandler myEventHandler3;
private Button mBtn1;
private Button mBtn2;
private Button mBtn3;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
mBtn1 = (Button) findComponentById(ResourceTable.Id_btn1);
mBtn2 = (Button) findComponentById(ResourceTable.Id_btn2);
mBtn3 = (Button) findComponentById(ResourceTable.Id_btn3);
mBtn1.setClickedListener(component -> initInnerEvent());
mBtn2.setClickedListener(component -> initRunnable());
mBtn3.setClickedListener(component -> initNewToOld());
runnerA = EventRunner.create(false);
runnerB = EventRunner.create(false);
runnerC = EventRunner.create(false);
myEventHandler1 = new EventHandler(runnerA) {
@Override
protected void processEvent(InnerEvent event) {
super.processEvent(event);
if (event == null) {
return;
}
int eventId = event.eventId;
switch (eventId) {
case 1: {
HiLog.info(hiLogLabel, "1");
break;
}
case 2: {
HiLog.info(hiLogLabel, "2");
break;
}
case 3: {
HiLog.info(hiLogLabel, "3");
break;
}
default:
break;
}
}
};
myEventHandler2 = new EventHandler(runnerB) {
@Override
protected void processEvent(InnerEvent event) {
super.processEvent(event);
}
};
myEventHandler3 = new EventHandler(runnerC) {
@Override
protected void processEvent(InnerEvent event) {
super.processEvent(event);
if (event == null) {
return;
}
int eventId = event.eventId;
Object object = event.object;
switch (eventId) {
case 1:
// 待執行的操作,由開發者定義
break;
case 2:
// 將原先執行緒的EventRunner例項投遞給新建立的執行緒
if (object instanceof EventRunner) {
EventRunner runner2 = (EventRunner) object;
}
// 將原先執行緒的EventRunner例項與新建立的執行緒的EventHandler繫結
EventHandler myHandler = new EventHandler(runnerC) {
@Override
public void processEvent(InnerEvent event) {
//需要在原先執行緒執行的操作
HiLog.info(hiLogLabel,"原執行緒操作");
}
};
InnerEvent event2 = InnerEvent.get(1, 0, null);
myHandler.sendEvent(event2); // 投遞事件到原先的執行緒
break;
default:
break;
}
}
};
}
/**
* EventHandler 投遞 InnerEvent 事件
*/
private void initInnerEvent() {
// 向執行緒A傳送事件
long param = 0;
Object object = EventRunner.current();
InnerEvent event = InnerEvent.get(1, param, object);
myEventHandler1.sendEvent(event);
runnerA.run();
runnerA.stop();
}
/**
* EventHandler 投遞 Runnable 任務
*/
private void initRunnable() {
Runnable task1 = () -> HiLog.info(hiLogLabel, "Runnable1執行");
Runnable task2 = () -> HiLog.info(hiLogLabel, "Runnable2執行");
myEventHandler2.postTask(task2, 0, EventHandler.Priority.IMMEDIATE);
myEventHandler2.postTask(task1, 0, EventHandler.Priority.IMMEDIATE);
runnerB.run();
runnerB.stop();
}
/**
* 在新建立的執行緒裡投遞事件到原執行緒
*/
private void initNewToOld() {
int eventId = 2;
long param = 0;
Object object = EventRunner.current();
InnerEvent event = InnerEvent.get(eventId, param, object);
myEventHandler3.sendEvent(event);
runnerC.run();
runnerC.stop();
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}