Post date: Feb 7, 2012 3:01:07 PM
BackgroundWorker e Invoke
Questo articolo può essere utile a chi non conosce il threading e le operazioni asincrone. Anche senza Asynchronous o BackgroundWorker si può creare una buona applicazione. Tuttavia, con le operazioni asincrone, è possibile migliorare il tempo di elaborazione e, quindi, l'applicazione sarà più performante. Il seguente codice di esempio lo dimostra.
Eseguire il codice sorgente sequenziale
Nell'evento btnStart_Click, è commentata la funzione BgWorkerFunction. Per eseguire senza asincronia si può commentare la chiamata della funzione BackgroundWorker1.RunWorkerAsync (), e, rimuovere il commento alla chiamata BgWorkerFunction. Ora si può notare che il primo ListBox viene caricato completamente prima che inizi il caricamento della seconda ListBox. In questo caso le operazioni sono sequenziali.
Eseguire con il BackgroundWorker
Ora, sempre nell’evento btnStart_Click , togli il commento a BackgroundWorker1.RunWorkerAsync () e commenta la chiamata a BgWorkerFunction. Si può vedere che entrambi i ListBoxes verranno caricati contemporaneamente. Così l'utente può vedere il progresso in due operazioni diverse ma collegate in parallelo.
Avviare BackgroundWorker
BackgroundWorker è un controllo utente (casella degli strumenti). Quindi, è possibile trascinarlo dalla casella degli strumenti sulla form. Prima di iniziare ad utilizzarlo sarà necessario specificare solo alcune proprietà. WorkerReportsProgress su true se avete bisogno di mostrare i progressi per l'utente. È possibile utilizzare l'evento ProgressChanged. Se tutto è configurato, RunWorkerAsync avvierà il processo.
BackgroundWorker.DoWork
Le operazioni che devono essere eseguite in background devono essere scritte qui. Tenere presente che sta processando in un altro thread NON in quello principale. Quindi c’è bisogno di invoke, se le comunicazioni tra thread non sono necessarie. È possibile utilizzare il metodo Invoke, e la proprietà InvokeRequired.
Accedere alla casella di riepilogo da BackgroundWorker utilizzando Invoke
Come già spiegato in precedenza, è necessario richiamarlo durante le comunicazioni tra thread. Nel nostro caso la ListBox è nel thread principale e BackgroundWorker gestisce le operazioni in un altro thread. Quando si esegue la funzione di BgWorkerFunction dall'evento btnStart_Click, non vi è alcun bisogno di invocarlo. Ma quando la stessa funzione viene chiamato dal BackgroundWorker.DoWork, vi è la necessità di Invoke. Ciò può essere rilevato nel runtime utilizzando la proprietà InvokeRequred. Questa proprietà è disponibile in ogni controllo nella casella degli strumenti.
Delegate per Invoke
La funzione ha bisogno di richiamare un delegate per accedere al controllo nel thread principale. Nel codice c’è un delegate nominato AddItemsToListBoxDelegate con la stessa firma di AddItemsToListBox. AddItemsToListBox ToListBox si aspetta due parametri, e il testo, quindi è necessario passare questi due valori quando si utilizza il delegate per invocare il metodo AddItemsToListBox. Ciò può essere ottenuto dal Invoke overload che accetta parametri per il metodo di destinazione come un array. E’ possibile utilizzare BackgroundWorker per richiamare i controlli nel thread principale.
Il codice di esempio - prelevato da: http://www.vbnettutorial.net/?Id=136&Desc=Vb.Net-BackgroundWorker-To-Invoke-ListBox (En)
Imports System.ComponentModel
Public Class Form1
Public Delegate Sub AddItemsToListBoxDelegate( _
ByVal ToListBox As ListBox, _
ByVal AddText As String)
Private Sub btnStart_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnStart.Click
BackgroundWorker1.RunWorkerAsync()
MainFunction()
'BgWorkerFunction()
End Sub
Private Sub MainFunction()
Dim StartDate = Now
Dim Counter = 0
Do Until Counter > 20
AddItemsToListBox(lstMainFunction, "Current Index is " & Counter)
Counter += 1
Threading.Thread.Sleep(100)
Application.DoEvents() 'if you dont instruct App to doevents,
'you will not see any background work, the work looks sequential.
Loop
End Sub
Private Sub BgWorkerFunction()
Dim StartDate = Now
Dim Counter = 0
Do Until Counter > 20
'if you call this from a thread which is not the main/form then
'invoke is required.
If (lstBgWorker.InvokeRequired) Then
lstBgWorker.Invoke( _
New AddItemsToListBoxDelegate(AddressOf AddItemsToListBox), _
New Object() {lstBgWorker, "Current Index is " & Counter})
Else
lstBgWorker.Items.Add("Current Index is " & Counter)
lstBgWorker.SelectedIndex = lstBgWorker.Items.Count - 1
End If
Counter += 1
Threading.Thread.Sleep(100)
Application.DoEvents()
Loop
End Sub
Private Sub AddItemsToListBox(ByVal ToListBox As ListBox, _
ByVal AddText As String)
ToListBox.Items.Add(AddText)
ToListBox.SelectedIndex = ToListBox.Items.Count - 1
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
ByVal e As DoWorkEventArgs) _
Handles BackgroundWorker1.DoWork
BgWorkerFunction()
End Sub
End Class
Designer(Designer.vb)
Imports System.Drawing
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.BackgroundWorker1 = New System.ComponentModel.BackgroundWorker
Me.lstMainFunction = New System.Windows.Forms.ListBox
Me.lstBgWorker = New System.Windows.Forms.ListBox
Me.lblMainFunction = New System.Windows.Forms.Label
Me.lblBgWorker = New System.Windows.Forms.Label
Me.btnStart = New System.Windows.Forms.Button
Me.SuspendLayout()
'
'BackgroundWorker1
'
Me.BackgroundWorker1.WorkerReportsProgress = True
'
'lstMainFunction
'
Me.lstMainFunction.Font = New Font("Microsoft Sans Serif", 9.75!, _
FontStyle.Bold, _
GraphicsUnit.Point, _
CType(0, Byte))
Me.lstMainFunction.FormattingEnabled = True
Me.lstMainFunction.ItemHeight = 16
Me.lstMainFunction.Location = New Point(2, 56)
Me.lstMainFunction.Name = "lstMainFunction"
Me.lstMainFunction.Size = New Size(137, 340)
Me.lstMainFunction.TabIndex = 0
'
'lstBgWorker
'
Me.lstBgWorker.Font = New Font("Microsoft Sans Serif", 9.75!, _
FontStyle.Bold, _
GraphicsUnit.Point, _
CType(0, Byte))
Me.lstBgWorker.FormattingEnabled = True
Me.lstBgWorker.ItemHeight = 16
Me.lstBgWorker.Location = New Point(158, 56)
Me.lstBgWorker.Name = "lstBgWorker"
Me.lstBgWorker.Size = New Size(137, 340)
Me.lstBgWorker.TabIndex = 1
'
'lblMainFunction
'
Me.lblMainFunction.AutoSize = True
Me.lblMainFunction.Font = New Font("Microsoft Sans Serif", 9.75!, _
FontStyle.Bold, _
GraphicsUnit.Point, _
CType(0, Byte))
Me.lblMainFunction.Location = New Point(-1, 40)
Me.lblMainFunction.Name = "lblMainFunction"
Me.lblMainFunction.Size = New Size(66, 16)
Me.lblMainFunction.TabIndex = 2
Me.lblMainFunction.Text = "Function"
'
'lblBgWorker
'
Me.lblBgWorker.AutoSize = True
Me.lblBgWorker.Font = New Font("Microsoft Sans Serif", 9.75!, _
FontStyle.Bold, _
GraphicsUnit.Point, _
CType(0, Byte))
Me.lblBgWorker.Location = New Point(160, 39)
Me.lblBgWorker.Name = "lblBgWorker"
Me.lblBgWorker.Size = New Size(145, 16)
Me.lblBgWorker.TabIndex = 3
Me.lblBgWorker.Text = "Background Worker"
'
'btnStart
'
Me.btnStart.Font = New Font("Microsoft Sans Serif", 9.75!, _
FontStyle.Bold, _
GraphicsUnit.Point, _
CType(0, Byte))
Me.btnStart.Location = New Point(2, 12)
Me.btnStart.Name = "btnStart"
Me.btnStart.Size = New Size(75, 23)
Me.btnStart.TabIndex = 4
Me.btnStart.Text = "Start"
Me.btnStart.UseVisualStyleBackColor = True
'
'Form1
'
Me.AutoScaleDimensions = New SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New Size(305, 397)
Me.Controls.Add(Me.btnStart)
Me.Controls.Add(Me.lblBgWorker)
Me.Controls.Add(Me.lblMainFunction)
Me.Controls.Add(Me.lstBgWorker)
Me.Controls.Add(Me.lstMainFunction)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
Me.PerformLayout()
End Sub
Friend WithEvents BackgroundWorker1 As System.ComponentModel.BackgroundWorker
Friend WithEvents lstMainFunction As System.Windows.Forms.ListBox
Friend WithEvents lstBgWorker As System.Windows.Forms.ListBox
Friend WithEvents lblMainFunction As System.Windows.Forms.Label
Friend WithEvents lblBgWorker As System.Windows.Forms.Label
Friend WithEvents btnStart As System.Windows.Forms.Button
End Class