c#并行任務(wù)多種優(yōu)化方案分享(異步委托)
遇到一個(gè)多線程任務(wù)優(yōu)化的問題,現(xiàn)在解決了,分享如下。
假設(shè)有四個(gè)任務(wù):
任務(wù)1:登陸驗(yàn)證(CheckUser)
任務(wù)2:驗(yàn)證成功后從Web服務(wù)獲取數(shù)據(jù)(GetDataFromWeb)
任務(wù)3:驗(yàn)證成功后從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)(GetDatFromDb)
任務(wù)4:使用2、3的數(shù)據(jù)執(zhí)行一個(gè)方法 (StartProcess)
一個(gè)比較笨的方法(本人最開始的方法,記為方法1)是直接開啟一個(gè)線程,按照順序依次執(zhí)行四個(gè)任務(wù):
new Thread(delegate
{
CheckUser();
GetDatFromDb();//從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)
GetDataFromWeb();//web服務(wù)獲取數(shù)據(jù)
StartProcess();//執(zhí)行4
}).Start();
但是仔細(xì)分析需求我們會(huì)發(fā)現(xiàn),任務(wù)2和任務(wù)3并沒有先后區(qū)別,事實(shí)上兩者并無(wú)關(guān)聯(lián),只不過任務(wù)4的執(zhí)行需要任務(wù)2和3都已完成作為條件,所以我們可以再開兩個(gè)線程用于執(zhí)行任務(wù)2和任務(wù)3,當(dāng)兩者都執(zhí)行完畢之后,執(zhí)行任務(wù)4。
在這里使用了兩個(gè)全局變量用于表示任務(wù)2和任務(wù)3的狀態(tài)。用三個(gè)線程分別執(zhí)行任務(wù)2、3、4,其中任務(wù)4一直在循環(huán)監(jiān)聽全局變量的狀態(tài),確保在2、3都執(zhí)行完畢后才執(zhí)行。
這記為方法2:
private static volatile bool _m2;//任務(wù)2的標(biāo)志位
private static volatile bool _m3;//任務(wù)3的標(biāo)志位
private static void Main(string[] args)
{
new Thread(delegate
{
CheckUser();
new Thread(delegate
{
GetDatFromDb();//從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)
_m2 = true;//標(biāo)志位置為true
}).Start();
new Thread(delegate
{
GetDataFromWeb();//web服務(wù)獲取數(shù)據(jù)
_m3 = true;//標(biāo)志位置為true
}).Start();
new Thread(delegate
{
while (!(_m3 && _m2))//判斷任務(wù)2和3是否已執(zhí)行完畢
{
Thread.Sleep(100);
}
StartProcess();//執(zhí)行任務(wù)4
_m2 = true;
}).Start();
}).Start();
}
以上代碼基本上已經(jīng)可以達(dá)到預(yù)期目標(biāo)了,但是由于借助了兩個(gè)全局變量,盡管在這里不會(huì)涉及到同步?jīng)_突的問題,但總覺得很不放心,而且當(dāng)我們需要做擴(kuò)展的時(shí)候,比方說(shuō)在執(zhí)行任務(wù)4之前,我們還需要加載文件內(nèi)的數(shù)據(jù)(GetDataFromFile),那我們必須再添加一個(gè)全局標(biāo)志位,顯得有點(diǎn)麻煩。
事實(shí)上,Thread類本身已經(jīng)擁有對(duì)這種情況的完美解決方案——join。
Thread.Join 方法
在繼續(xù)執(zhí)行標(biāo)準(zhǔn)的 COM 和 SendMessage 消息泵處理期間,阻塞調(diào)用線程,直到某個(gè)線程終止為止。
簡(jiǎn)單來(lái)說(shuō),join就是個(gè)阻塞方法,在線程1內(nèi)創(chuàng)建線程2,調(diào)用線程2的Join方法,那么線程1將會(huì)被阻塞,直到線程2執(zhí)行完畢。運(yùn)用到上面的例子就是,在任務(wù)4內(nèi)創(chuàng)建任務(wù)2和任務(wù)3的線程,調(diào)用任務(wù)2和任務(wù)3的線程的Join方法使任務(wù)4阻塞,直到任務(wù)2和任務(wù)3執(zhí)行完畢,才繼續(xù)執(zhí)行任務(wù)4。這記為方法3,代碼如下
private static void Main(string[] args)
{
new Thread(delegate
{
CheckUser();
new Thread(delegate
{
Thread task2 = new Thread(delegate
{
GetDatFromDb(); //從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)
});
Thread task3 = new Thread(delegate
{
GetDataFromWeb(); //web服務(wù)獲取數(shù)據(jù)
});
task2.Start();
task3.Start();
task2.Join();//任務(wù)2阻塞
task3.Join();//任務(wù)3阻塞
StartProcess(); //執(zhí)行任務(wù)4
}).Start();
}).Start();
}
這樣便不需要任何標(biāo)志位了。這是最理想的解決方案。
另外還有一種解決方案,使用EventWaitHandle
EventWaitHandle 類允許線程通過發(fā)出信號(hào)和等待信號(hào)來(lái)互相通信。事件等待句柄(簡(jiǎn)稱事件)就是可以通過發(fā)出相應(yīng)的信號(hào)來(lái)釋放一個(gè)或多個(gè)等待線程的等待句柄。信號(hào)發(fā)出后,可以用手動(dòng)或自動(dòng)方式重置事件等待句柄
簡(jiǎn)單來(lái)說(shuō),就是方法2的進(jìn)階版,使用EventWaitHandle控制狀態(tài),而不再使用While循環(huán)監(jiān)聽,但這里仍舊需要兩個(gè)全局的EventWaitHandle對(duì)象。該方法記為方法4,代碼如下
private static EventWaitHandle eventWait1 = new EventWaitHandle(false, EventResetMode.AutoReset);//初始化狀態(tài)false;
private static EventWaitHandle eventWait2 = new EventWaitHandle(false, EventResetMode.AutoReset);//初始化狀態(tài)false;
private static void Main(string[] args)
{
new Thread(delegate
{
CheckUser();
new Thread(delegate
{
GetDatFromDb(); //從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)
eventWait1.Set(); //標(biāo)志位置為true
}).Start();
new Thread(delegate
{
GetDataFromWeb(); //web服務(wù)獲取數(shù)據(jù)
eventWait2.Set(); //標(biāo)志位置為true
}).Start();
new Thread(delegate
{
eventWait1.WaitOne();//任務(wù)2阻塞,等待
eventWait2.WaitOne();//任務(wù)3阻塞,等待
StartProcess(); //執(zhí)行任務(wù)4
}).Start();
}).Start();
}
上述三個(gè)優(yōu)化方案,其實(shí)核心思想都是一樣的,都是通過開啟3個(gè)線程分別執(zhí)行2、3、4任務(wù),其中任務(wù)4被阻塞(while循環(huán)、eventWait.WaitOne,thread.join),當(dāng)阻塞解除后,繼續(xù)執(zhí)行任務(wù)4。也就是說(shuō),任務(wù)4,其實(shí)是一直在等待任務(wù)2和任務(wù)3的完成。那么,是否有辦法讓任務(wù)2和任務(wù)3主動(dòng)通知任務(wù)4呢?即,任務(wù)2和任務(wù)3完成后,主動(dòng)執(zhí)行任務(wù)4。
方法當(dāng)然有:異步委托+回調(diào)函數(shù)
private static object obj = new object();
private static volatile bool _m2;//任務(wù)2的標(biāo)志位
private static volatile bool _m3;//任務(wù)3的標(biāo)志位
private static void Main(string[] args)
{
CheckUser(); //第一步 驗(yàn)證用戶
Action step2 = delegate
{
GetDatFromDb(); //從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)
_m2 = true; //標(biāo)志位置為true
};
Action step3 = delegate
{
GetDataFromWeb(); //web服務(wù)獲取數(shù)據(jù)
_m3 = true; //標(biāo)志位置為true
};
step2.BeginInvoke(delegate
{
if (_m2 && _m3) //通過標(biāo)志位判斷2 3是否都已完成
{
lock (obj)//加鎖
{
_m2 = false;
if (_m3)//二重驗(yàn)證 防止兩者同時(shí)進(jìn)入
StartProcess(); //執(zhí)行4
}
}
}, null);
step3.BeginInvoke(delegate
{
if (_m2 && _m3) //通過標(biāo)志位判斷2 3是否都已完成
{
lock (obj)
{
_m3 = false;
if (_m2)
StartProcess(); //執(zhí)行4
}
}
}, null);
}
講解下代碼。首先以委托的方式創(chuàng)建了任務(wù)2和任務(wù)3的委托對(duì)象step2和step3。執(zhí)行這兩個(gè)委托的異步調(diào)用方法BegInvoke。執(zhí)行BegInvoke,會(huì)創(chuàng)建一個(gè)新的線程來(lái)執(zhí)行step2和step3的方法,同時(shí),在執(zhí)行BeginInvoke的時(shí)候還指定了一個(gè)回調(diào)函數(shù)
delegate
{
if (_m2 && _m3) //通過標(biāo)志位判斷2 3是否都已完成
{
lock (obj)
{
_m3 = false;
if (_m2)
StartProcess(); //執(zhí)行4
}
}
}
這個(gè)函數(shù)會(huì)在step2和step3的線程執(zhí)行完畢后被調(diào)用。在這里,我再次使用了標(biāo)志位來(lái)判斷step2和step3是否已經(jīng)運(yùn)行完成,同時(shí),為了防止一種特殊情:“step2和step3所執(zhí)行的時(shí)間幾乎相等,他們會(huì)同時(shí)通過if(_m2&&_m3)判斷,進(jìn)而執(zhí)行兩次StartProcess” 在這里加了lock鎖,并且在lock鎖內(nèi)將標(biāo)志位重置+二重判斷(這里可以參考單例模式的雙重鎖定原理),確保StarProcess只會(huì)執(zhí)行一次。
如此這般,一個(gè)主動(dòng)通知模式的并行任務(wù)便實(shí)現(xiàn)了,不過,這種實(shí)現(xiàn)方法相較于方法2,實(shí)在太過麻煩,尤其在與并發(fā)處理方面,個(gè)人感覺實(shí)用性不太高。
另外,還可以使用觀察者模式實(shí)現(xiàn)異步委托+回調(diào)函數(shù)的效果。
相關(guān)文章
.Net WInform開發(fā)筆記(五)關(guān)于事件Event
我前面幾篇博客中提到過.net中的事件與Windows事件的區(qū)別,本文討論的是前者,也就是我們代碼中經(jīng)常用到的Event,感興趣的朋友可以了解下2013-01-01C#實(shí)現(xiàn)根據(jù)指定容器和控件名字獲得控件的方法
這篇文章主要介紹了C#實(shí)現(xiàn)根據(jù)指定容器和控件名字獲得控件的方法,其中包括了遍歷與遞歸的應(yīng)用,需要的朋友可以參考下2014-08-08C#開發(fā)之int與string轉(zhuǎn)化操作
這篇文章主要介紹了C#開發(fā)之int與string轉(zhuǎn)化操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2020-12-12WCF實(shí)現(xiàn)的計(jì)算器功能實(shí)例
這篇文章主要介紹了WCF實(shí)現(xiàn)的計(jì)算器功能,結(jié)合具體實(shí)例形式較為詳細(xì)的分析了WCF實(shí)現(xiàn)計(jì)算器功能的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-06-06Winform 實(shí)現(xiàn)進(jìn)度條彈窗和任務(wù)控制
這篇文章主要介紹了Winform 實(shí)現(xiàn)進(jìn)度條彈窗和任務(wù)控制的方法,幫助大家更好的利用c# winform進(jìn)行開發(fā),感興趣的朋友可以了解下2020-12-12Unity Shader相交算法實(shí)現(xiàn)簡(jiǎn)易防能量盾
這篇文章主要為大家詳細(xì)介紹了Unity Shader相交算法實(shí)現(xiàn)簡(jiǎn)易防能量盾,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04