VB.NET — Выполнение работы в отдельном потоке, чтобы предотвратить зависание формы

#.net #vb.net #multithreading #backgroundworker

#.net #vb.net #многопоточность #backgroundworker

Вопрос:

У меня есть действительно простая форма с кнопкой, которая запускает созданный мной подраздел, который собирает данные из ActiveDirectory и добавляет их в таблицу Excel.

Проблема в том, что когда я нажимаю эту кнопку, зависает вся форма. Итак, я решил, что операция, которая собирает данные и добавляет их на лист Excel, должна выполняться в собственном потоке, чтобы форма не зависала. Возможно, было бы здорово также добавить панель прогресса. Панель выполнения, однако, расположена в основной пользовательской форме, которая запускается после запуска проектов.

Что мне нужно сделать, чтобы получить это так, как я хочу?

Редактировать: Добавлен некоторый мой код. У меня есть один MainForm.vb и один CodeFile.vb. Я хочу, чтобы большая часть кода была в CodeFile.vb, чтобы было аккуратнее.

MainForm.vb

 Imports User_edit.CodeFile
Imports System.ComponentModel

Public Class MainForm
    Private Sub btnImportData_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnImportData.Click
        If MyBackgroundWorker.IsBusy <> True Then
            MyBackgroundWorker.RunWorkerAsync()
        End If
    End Sub

    Private Sub BackgroundWorker_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles MyBackgroundWorker.DoWork
        ExportADUsers()
    End Sub

    Private Sub BackgroundWorker_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles MyBackgroundWorker.ProgressChanged
        statusBarLabel.Text = (e.ProgressPercentage.ToString)
    End Sub

    Private Sub BackgroundWorker_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles MyBackgroundWorker.RunWorkerCompleted
        statusBarLabel.Text = "Finished"
    End Sub
End Class
  

CodeFile.vb

 Imports System.DirectoryServices
Imports System.ComponentModel
Imports System.Threading

Module CodeFile
    Public Sub ExportADUsers()
        MainForm.MyBackgroundWorker.WorkerReportsProgress = True
        MainForm.MyBackgroundWorker.WorkerSupportsCancellation = True

        Dim i As Integer

        Dim objRootDSE, strRoot, strfilter, strAttributes, strScope
            objRootDSE = GetObject("LDAP://RootDSE")
            strRoot = objRootDSE.GET("DefaultNamingContext")
            strfilter = "(amp;(objectCategory=Person)(objectClass=User))"
            strAttributes = "mail,userPrincipalName,givenName,sn," amp; _
              "initials,displayName,physicalDeliveryOfficeName," amp; _
              "telephoneNumber,mail,wWWHomePage,profilePath," amp; _
              "scriptPath,homeDirectory,homeDrive,title,department," amp; _
              "company,manager,homePhone,pager,mobile," amp; _
              "facsimileTelephoneNumber,ipphone,info," amp; _
              "streetAddress,postOfficeBox,l,st,postalCode,c"
            'Scope of the search.  Change to "onelevel" if you didn't want to search child OU's
            MainForm.statusBarLabel.Text = "Collecting data"
        strScope = "subtree"

            Dim cn, cmd, rs
            cn = CreateObject("ADODB.Connection")
            cmd = CreateObject("ADODB.Command")

            cn.open("Provider=ADsDSOObject;")
            cmd.ActiveConnection = cn
            cmd.commandtext = "<LDAP://" amp; strRoot amp; ">;" amp; strfilter amp; ";" amp; _
                                strAttributes amp; ";" amp; strScope

            rs = cmd.EXECUTE

            Dim objExcel, objWB, objSheet

            objExcel = CreateObject("Excel.Application")
            objWB = objExcel.Workbooks.Add
        objSheet = objWB.Worksheets(1)

        For i = 0 To rs.Fields.Count - 1
            MainForm.MyBackgroundWorker.ReportProgress(i * 10)
            objSheet.Cells(1, i   1).Value = rs.Fields(i).Name
            objSheet.Cells(1, i   1).Font.Bold = True
        Next

            Dim strExportFile
            strExportFile = "C:usersvsandodesktopexport.xls"

            objSheet.Range("A2").CopyFromRecordset(rs)
            objSheet.SaveAs(strExportFile)

            'Clean up
            rs.Close()
            cn.Close()
            objSheet = Nothing
            objWB = Nothing
            objExcel.Quit()
            objExcel = Nothing

    End Sub
  

Обратите внимание на ExportFromAD подраздел, который у меня есть в CodeFile.vb . Это то, что на самом деле выполняет работу. В цикле For each, который добавляет данные в Excel, я поместил MainForm.MyBackgroundWorker.ReportProgress(i * 10) .

Проблема в том, что на самом деле это не обновляет метку в форме. Что я нахожу довольно странным, потому что форма на самом деле не висит или что-то в этом роде. Он пытается получить доступ к другому потоку или что-то в этом роде? То есть форма запускается в своем собственном потоке, к которому невозможно получить доступ из моего второго потока?

Комментарии:

1. Вы уже получили ответ в одном из своих тегов: самый простой способ сделать это — использовать BackgroundWorker компонент. Пример реализации на MSDN уже очень хорош.

2. Из примера кода: If (worker.CancellationPending = True) Then … фу! И снова они позволяют стажеру написать пример.

3. @Konrad: Фу, я согласен. Но более вероятно, что они сделали это в тщетной попытке «прояснить» код.

Ответ №1:

Лучше всего использовать BackgroundWorker , поскольку этот класс разработан именно для этого варианта использования.

Это также позволяет вам перезванивать форме для обновления строки состояния.

Комментарии:

1. Пожалуйста, ознакомьтесь с обновленной темой. Я попытался добавить фонового рабочего. И я думаю, что это работает, но я не могу заставить индикатор выполнения или метку statusstrip фактически обновляться. Я попытался добавить точку останова, чтобы увидеть фактическое значение метки во время выполнения. И это фактически установлено. Это просто не отображается. Вот почему я считаю, что он пытается отобразить метку, которая находится в другом потоке?

Ответ №2:

Класс BackgroundWorker — это то, что вам нужно использовать. Для передачи данных обратно в индикатор выполнения формы вы устанавливаете для WorkerReportsProgress свойства значение true и обрабатываете ProgressChanged событие, чтобы установить значение индикатора выполнения. Из метода длительного выполнения вы можете отправлять прогресс следующим образом:

 backgroundworker.ReportProgress(10)