多線程的問題和一些學習感悟

來源:互聯網
上載者:User

寫一個小軟體的時候碰到了一個問題。有一串很耗時的函數需要處理。基本流程如下:

private void Sample()
{
    aMethodNeedLongTime();//一個很耗時的計算函數
    aMethodNeddLongTimeRefWithUI();//一個很耗時的與UI控制項處理相關的函數
}

很顯然,執行這個函數介面會無法響應。為瞭解決這個問題,我幾乎是不假思索的寫下了下面這樣的代碼:

private void Sample()
{
    Thread oneStep=new Thread(new ThreadStart(aMethodNeedLongTime);
    oneStep.Start();
    oneStep.Join();

    aMethodNeddLongTimeRefWithUI();//UI相關的內容是無法這樣使用多線程的,這個問題等會再說
}

效果沒有任何變化,介面在計算過程中依然沒有響應。仔細想下,發現了問題。我開了一個新的線程,然後馬上用它堵塞了主線程,使得主線程無法與新的進程並行工作,所以介面不會有響應。這是一個很低級的錯誤。因為這裡錯誤的理解了多線程的用途。多線程適用於平行處理事件,使不同線程上的多個事件可以同時響應,而我用了串列的思想應用了它,使得多線程並行變成了多線程輪流執行,很是汗顏,真是枉費了我學那麼久的作業系統了。
但仔細回憶下,我自所以如此自然的寫下了上面的代碼,是因為在公司實習的時候,某前輩的代碼就是這樣寫的。那時一個Console的程式,在Main函數中前輩用了大量的類似上面的代碼來處理極其耗時資料庫操作函數。代碼類似於:

static void Main(string[] args)
{
    Thread oneStep=new Thread(new ThreadStart(aMethodNeedLongTime);
    oneStep.Start();
    oneStep.Join();
    ...
}

而且實際效果頗有多線程的風範,在資料庫操作時,Console視窗可以可以自由的移動。現在仔細想想,可能是因為這是Console程式,並沒有啟動一個Application。而Console視窗的繪製另有線程負責,這是與通常的WinForm程式不同的。(不知道想的對不對)也就是直接使用:
static void Main(string[] args)
{
    aMethodNeedLongTime();
    ...
}
效果比上面的效果還要好(至少省去了開一個線程的時間)。
但是最初看到這樣代碼的時候,我遺忘了思考。看到了效果,卻忘了仔細想想達到這種效果的實際原因,而只是簡單的相信了所謂的前輩寫的代碼。這種態度很要不得。人都說,做事需要經驗,我想經驗是在思考中得來的,經曆過的東西不能就這樣讓他在手邊白白流走,甚至留下了一大堆的垃圾。改正,下次不再放過理解不了的疑點,不再偏信任何無法理解的東西。不過讓我比較後怕的事,就這樣的代碼竟然成天在DELL的機器中運行著(我們給DELL做外包)。我反正是沒臉回公司了,怕我的繼任者拍著我的頭說,孩子,這種代碼是你寫的吧。哎,真太汗了。
言歸正傳。為瞭解決這個問題,上csdn請教了一些前輩。很多人說,僅是為瞭解決介面響應問題,不需要使用多線程,而是利用Application.DoEvent()來做。Application.DoEvent()是沿用的VB的一貫做法(再汗一個,就我這樣的實在無法說是用過VB啊)。在耗時函數(比如上面的aMethodNeedLongTime)中的某個部位嵌入Application.DoEvent()(一般是嵌在迴圈體中,使其可以定時執行)。其作用是通知Application先去處理一下隊列中的其他事件,再來處理耗時的運算。但我無法將其嵌入到我的aMethodNeedLongTime中去(這種情況很多,也許這個方法被封裝了,也許方法中沒有迴圈體可以插入,也許和我一樣討厭插入不倫不類的東西)。於是我用了一個Timer來定時處理。代碼如下:
private void Sample()
{
    timer1.Enable=true;

    aMethodNeedLongTime;

    timer1.Enable=false;
}
private void Timer1_Tick(object sender,EventArgs e)
{
    Application.DoEvent();
}
很出乎我意料,這段代碼竟然基本可以達到目的。我本以為這一個是個死迴圈。因為我以為Tick也需要在事件隊列中排隊,會同樣被耗時運算堵塞無法執行,因此這段代碼沒有效果。但實際上,介面基本達到了可以正常晃動的程度。也就是說可能Tick事件另有線程來處理,也可能Tick事件優先順序較高,可以提前得到處理。在msdn中沒查到相關的解釋,期待某個有研究的前輩可以指點。
也有人讓我繼續使用多線程來處理。串列的處理,可以利用輪詢,通訊(這是我想的,還沒有具體學習)等來解決。但更直接的解決辦法是把上面的aMethodNeedLongTime()和aMethodNeddLongTimeRefWithUI()放在一個線程中執行。但是這裡的aMethodNeddLongTimeRefWithUI()是一個與UI控制項相關的方法,通常UI控制項是不允許其他線程操作的,其原因,愚翁(一個csdn上C#版的四星名人)在其blog中是這樣說的:因為自訂的線程和UI線程是在不同地方建立的。有一些感性的認識,但不是很理性,等我問清楚以後再寫好了。當然如果一定要做的話,還是有方法的(不然進度條都不用做了)。利用invoke函數。這個問題微軟說的很爛。在愚翁的Blog中有很好的說明,參見:
http://blog.csdn.net/Knight94/archive/2006/05/27/757351.aspx
http://blog.csdn.net/knight94/archive/2006/03/16/626584.aspx
利用這個,我終於把兩個函數仍在了一個線程中執行。可以看出來,整個流程是一個很固定的模式。於是,自然會有人幫我們做好組件。這個人就是微軟。在VS2005中(其實是.net2.0),添加了一個BackgroudWorker的組件,這個組件專門用於處理非同步,大運算量的事件。它會幫你開好新的線程。這一次微軟的文檔寫的還算不錯,可以參見:
ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_fxmclictl/html/5b56e2aa-dc05-444f-930c-2d7b23f9ad5b.htm
ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.NETDEVFX.v20.chs/cpref3/html/T_System_ComponentModel_BackgroundWorker.htm
一些個人的說明就是,雖然文檔中善意的提醒:您必須非常小心,確保在 DoWork 事件處理常式中不操作任何使用介面物件。而應該通過 ProgressChanged 和 RunWorkerCompleted 事件與使用者介面進行通訊。但實際上,我們還是可以利用invoke將UI對象的處理邏輯也添加到DoWork事件中去。還有就是如果需要處理Cancel或進度條,要將WorkerReportsProgress和WorkerSupportsCancellation屬性設為True(預設是false)。而具體的Cancel和進度運算需要在DoWork中的大運算量函數中嵌入相關的邏輯。所以沒有特別需求可以不使用。
問題至此,算是初步解決了,雖然還留下了一堆的尾巴。但還有許多新的問題需要處理。比如線上程幕後處理是,前台的事件如何控制,多線程對效能的影響,等等。總之,又想起那句老話,掌握一種力量很容易,但學會使用這種力量卻很困難。多多思考和積累解決思路,多多學會權衡和取捨也許才更為重要。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.