VB.NET 初涉線程的定義和調用


什么是線程

說話一:進程是具有一定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位.

線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.

線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源.

一個線程可以創建和撤銷另一個線程;同一個進程中的多個線程之間可以並發執行

說法二:進程和線程都是由操作系統所體會的程序運行的基本單元,系統利用該基本單元實現系統對應用的並發性。進程和線程的區別在於:

簡而言之,一個程序至少有一個進程,一個進程至少有一個線程.

線程的划分尺度小於進程,使得多線程程序的並發性高。

另外,進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。

線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。

從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分可以同時執行。但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。

•線程(thread),有時也被稱為輕量級進程(lightweight process , LWP)
–線程是CPU使用的基本單元
–線程由如下部分組成:
       •線程ID
       •PC指針
       •一組寄存器
       •調用棧
 
單線程進程和多線程進程
 
–同一進程內的所有線程共享代碼段、數據段和其它操作系統資源 (如文件、信號等)。
 
為什么使用線程

我們一般編寫的程序代碼總是從 main 函數(控制台),Sub New()(類的構造函數),Load(窗體加載)開始執行的,從上往下,執行每一個調用,有明確的先后順序,一個sub或者function完成之后,才進行下一個sub或者function。的確,這樣程序的邏輯性很強,每個過程排隊,依次來。
 但是,這樣必然會在一定程度上降低應用程序的運行效率,如果某個過程代碼很長,所需要的時間長,那么程序在執行這個過程的時候會出現“假死”,停止響應用戶操作,知道過程全部執行完畢。
 關於“假死”:我們編寫的Windows應用程序有一個UI線程,用於接收和響應用戶界面的操作。而我們編寫的代碼一般都是基於這個線程的,位於單一線程中的代碼也是從上往下依次進行,所以當UI線程中某一過程花費的時間很長時,界面不再響應,因為它很忙,這時就出現了長時間的停頓,也就是“假死”,而用戶會認為這很卡。
 因此,如果我們在UI線程的基礎上另開一個線程,讓代碼分支執行的話,就不會卡了。但同時,我們又不得不面臨這樣一個問題:萬一線程執行的過程,和UI執行的過程有沖突怎么辦?當你在非UI線程中調用UI線程中的某一個控件,設置它的某某屬性,這時你會收到這樣一條錯誤:
 →線程間操作無效: 從不是創建控件“xxx”的線程訪問它。
怎么辦啊? 別急,后文會有解釋。

首先,我們來體驗一下使用線程帶來的好處和問題。

1.創建Windows窗體應用程序隨便弄個名。
2.在窗體上放兩個控件,Label1個Button1,如圖,其他屬性默認:

我們想實現這樣一個功能:在Label1上面動態顯示數字,從0~9000,我們希望看到數字的變化。

Public Class Form1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Call testA()

    End Sub

    Private Sub testA()

        For i = 0 To 9000

            Label1.Text = i.ToString

        Next

    End Sub

End Class

意思就是點擊按鈕,進入test過程中,通過For循環,依次顯示數字,真是這樣嗎?運行試試...
不出我意料的話,最后直接顯示的是9000,中間還卡了一下。后面的0都不在了。。

那么,我們要顯示動態變化又怎么辦呢?

我們把上面的代碼修改一下,使用線程。

Imports System.Threading '導入線程命名空間
Public Class Form1
    Dim t As Thread  '定義一個全局的線程變量
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        t = New Thread(AddressOf testA)  '創建線程,使它指向 TestA 過程,注意該過程不能帶有參數
        t.Start() '啟動線程
    End Sub
    Private Sub testA()
        For i = 0 To 9000
            Label1.Text = i.ToString
        Next
        t.Abort() '運行完后終止線程
    End Sub
End Class

再次運行,點擊確定,出錯啦?什么錯?如圖:

由於是從一個新的線程調用UI線程中窗體控件,所以這個做法很危險,你直接被拒絕了。
有一個解決辦法,就是讓編譯器不進行跨線程檢查。

就是在 Click 代碼第一行加一句:
CheckForIllegalCrossThreadCalls = False

 CheckForIllegalCrossThreadCalls 方法獲取或設置一個值,該值指示是否捕獲對錯誤線程的調用,它在調試期間訪問的是空間的句柄,如果該值設置為 False,則表示禁止軟件對於不符合原則的跨線程運行的程序進行檢查。更為簡單的理解就是------忽略程序跨越線程運行導致的錯誤。

如下代碼:

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click     
        CheckForIllegalCrossThreadCalls = False   '忽略程序跨越線程運行導致的錯誤。   
        t = New Thread(AddressOf testA)  '創建線程,使它指向test過程,注意該過程不能帶有參數
        t.Start() '啟動線程
    End Sub

也可以針對某類控件進行設置,例如:

 Button.CheckForIllegalCrossThreadCalls = false

再次運行程序,就不會有錯了,你還能看見動態變化,並且沒有“假死”。如上就是線程的好處。

但是:
CheckForIllegalCrossThreadCalls = False
一句跨線程調用Windows窗體控件就萬能了嗎?畢竟這種方式很不優秀。

CheckForIllegalCrossThreadCalls 容許子線呈隨時更新ui,在同一個test函數體內,不能保證自身事務的一致性。給 Label1 付了值,一回頭就已經被別人改了,這是多么無語和暴走心情。  如果你覺的你的應用不會考慮在寫入ui的同時來讀取ui,而傾向使用CheckForIllegalCrossThreadCalls來追求效率的話,也是不恰當的做法。

首先 CheckForIllegalCrossThreadCalls 並不能讓效率發生本質的變化。 其次需求永遠是變化的,現在不考慮不等於以后不會碰到

當然你可以自己加鎖,用信號量,這樣還不如直接使用Invoke了,你只是又把別人做好的事情做了一遍。

不然,請看下文。

我希望通過Form1的按鈕,讓Form2中的Label0顯示0~9000.

代碼如下:

Imports System.Threading
Public Class Form1
    Dim t As Thread
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Form2.Show()
        Button.CheckForIllegalCrossThreadCalls = False
        t = New Thread(AddressOf testA)
        t.Start()
    End Sub
    Private Sub testA()
        For i = 0 To 9000
            Form2.Label1.Text = i.ToString '注意,這里改成 Form2 窗體上的標簽顯示
        Next
        t.Abort()
    End Sub
End Class

運行試試,咦?Form2里面怎么沒變?如圖:


 難道沒有執行那句代碼?
添加斷點看看?很明顯執行了。但是就是沒顯示,程序不聽話了?
CheckForIllegalCrossThreadCalls = False 沒轍了嗎?

看后文。

跨兩個UI調用CheckForIllegalCrossThreadCalls = False 
確實不太給力,那么如何是好?

這里,我們就要用到“委托”和 invoke? 什么東東啊? 往后看。。。
在 Form1 里面添加委托聲明代碼(帶一個參數),和控件更新過程(帶一個參數),
在 testA 中使用 Me.Invoke 調用委托,執行UpdateUI,並向里面傳一個參數 i
稍微修改一下,其余代碼不變:

Imports System.Threading
Public Class Form1
    Dim t As Thread
    Public Delegate Sub ToThread(ByVal setValue As Integer)  '聲明一個公開帶整形參數的委托
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Form2.Show()
        Button.CheckForIllegalCrossThreadCalls = False
        t = New Thread(AddressOf testA)
        t.Start()
    End Sub
    Private Sub testA()  '委托人,委托 UpdateUI 方法把我的結果告訴窗體2中的標簽,讓它顯示
        For i = 0 To 9000
            Dim ivo As New ToThread(AddressOf UpdateUI)  '實例化委托,並指向被委托的方法
            Invoke(ivo, i)  '用 Invoke 調用委托,並傳遞參數      
        Next
        t.Abort()
    End Sub

    '中間人、媒介人、被委托人(方法),代替textA 去告訴窗體2中的標簽,讓它把 TextA 事件傳遞過來的結果顯示出來。
    Private Sub UpdateUI(ByVal value As Integer) 
        Form2.Label1.Text = value.ToString
    End Sub
End Class

下面運行試試?你看到了什么?是不是動態變化了哦?

刪除這一句:CheckForIllegalCrossThreadCalls = False 也行。

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Form2.Show()
        t = New Thread(AddressOf testA)
        t.Start()
    End Sub

如圖:

 

那么,像這樣跨線程調用Windows窗體控件就實現了,並且這是被允許的安全方法。有了線程和委托的聯合,我們就能創建更加人性化的程序了,快速而又安全。多線程的實現就是開很多線程罷了,記住最后一定要.Abort關閉線程,不然如果線程未結束,程序退出只是UI退出,線程還在呢....

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM