Как использовать Parallel.ForEach для преобразования в DataGridViewRow

#vb.net #parallel.foreach

#vb.net #parallel.foreach

Вопрос:

Ниже у меня есть код, который выполняет цикл по каждой строке в datagridview, который содержит сервер и имя службы и использует ссылку ServiceController для проверки состояния каждой службы и возврата значения в ячейке.

         For Each dgvrow As DataGridViewRow In DataGridView1.Rows
        myController = New ServiceController With {
        .MachineName = dgvrow.Cells(0).Value,
        .ServiceName = dgvrow.Cells(1).Value
        }
        dgvrow.Cells(2).Value = myController.Status.ToString
        Next
  

Это работает, но выполняется последовательно, и каждому потоку требуется время для завершения, прежде чем он перейдет к следующей строке, поэтому я хочу запустить его параллельно.

Я искал здесь и наткнулся на Parallel.ForEach, но я не смог найти правильный код / комбинацию, чтобы заставить это работать.

Моя первоначальная попытка была

 Parallel.ForEach(dgvrow as DataGridViewRow in DatagridView1.Rows
Sub(myServer)
    myController = New ServiceController With {
        .MachineName = dgvrow.Cells(0).Value,
        .ServiceName = dgvrow.Cells(1).Value
        }
        dgvrow.Cells(2).Value = myController.Status.ToString
End Sub
)
  

Что определенно неправильно, не уверен, что поместить после части ForEach

Ожидаемый результат должен выглядеть следующим образом, я ожидаю, что столбец состояния сервиса будет заполнен одновременно.

 <table border=1>
  <tr>
    <th>Server Name</th>
    <th>Service Name</th> 
    <th>Service Status</th>
  </tr>
  <tr>
    <th>Server 1</th>
    <th>Service 1</th> 
    <th>Not Running</th>
  </tr>
  <tr>
    <th>Server 2</th>
    <th>Service 2</th> 
    <th>Running</th>
  </tr>
  <tr>
    <th>Server 3</th>
    <th>Service 3</th> 
    <th>Not Running</th>
  </tr>
  </table>
  

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

1. DataGridView это элемент пользовательского интерфейса — вам не разрешен доступ к нему из любого потока, кроме пользовательского интерфейса. Вы не можете использовать Parallel.ForEach для этого. Что вы пытаетесь вычислить, что заставляет вас захотеть распараллелить вычисления?

2. Вы пытаетесь обновлять статусы в реальном времени?

3. В конечном итоге, да. Я пытаюсь выяснить, как получить статус на parallel, поскольку это будет выполняться на списке из более чем 20 серверов, и требуется некоторое время, чтобы проверить каждый из них, не выполняя parallel.

4. Похоже, это лучшая работа для Microsoft Reactive Framework (Rx).

Ответ №1:

DataGridView это элемент пользовательского интерфейса — вам не разрешен доступ к нему из любого потока, кроме пользовательского интерфейса. Вы не можете использовать Parallel.ForEach для этого.

Что вы можете сделать, это извлечь данные как объект, отличный от пользовательского интерфейса, поработать над этим параллельно, а затем присвоить результаты обратно.

Попробуйте это:

 'UI thread
Dim inputs = DataGridView1.Rows.Cast(Of DataGridViewRow).Select(Function(dgvrow) New With _
{
    .MachineName = dgvrow.Cells(0).Value,
    .ServiceName = dgvrow.Cells(1).Value
}).ToArray()

'Parallel
Dim serviceControllers = inputs.AsParallel().Select(Function (x) New ServiceController With _
{
    .MachineName = x.MachineName,
    .ServiceName = x.ServiceName
}).ToArray()

'UI thread
For Each x In serviceControllers.Zip(DataGridView1.Rows.Cast(Of DataGridViewRow), Function (sc, dgvrow) New With { sc, dgvrow })
    x.dgvrow.Cells(2).Value = x.sc.Status.ToString
Next
  

Используйте это, если порядок строк меняется во время вычисления.

 'UI thread
Dim inputs = DataGridView1.Rows.Cast(Of DataGridViewRow).Select(Function(dgvrow) New With _
{
    .MachineName = dgvrow.Cells(0).Value,
    .ServiceName = dgvrow.Cells(1).Value,
    .dgvrow = dgvrow
}).ToArray()

'Parallel
Dim results = inputs.AsParallel().Select(Function (x) New With
{
    .sc = New ServiceController With _
    {
        .MachineName = x.MachineName,
        .ServiceName = x.ServiceName
    }, _
    .dgvrow = x.dgvrow
}).ToArray()

'UI thread
For Each x In results
    x.dgvrow.Cells(2).Value = x.sc.Status.ToString
Next
  

Имейте в виду, что если строки будут удалены во время вычисления, это также приведет к сбою.


Вы должны использовать Microsoft Reactive Framework (он же Rx) — NuGet System.Reactive.Windows.Forms и add using System.Reactive.Linq; — тогда вы сможете это сделать:

 'UI thread
Dim inputs = DataGridView1.Rows.Cast(Of DataGridViewRow).Select(Function(dgvrow) New With _
{
    .MachineName = CType(dgvrow.Cells(0).Value, String),
    .ServiceName = CType(dgvrow.Cells(1).Value, String),
    .Row = dgvrow
}).ToArray()

'Rx query
Dim query = _
    From x In inputs.ToObservable()
    From s In Observable.Start(Function () FetchStatus(x.MachineName, x.ServiceName))
    Select New With
    {
        x.Row,
        .Status = s
    }

'Rx subscription
Dim subscription As IDisposable = _
    query _
        .ObserveOn(DataGridView1) _
        .Subscribe(Sub (x) x.Row.Cells(2).Value = x.Status)
  

Это обновит каждую строку так же быстро, как придет ответ, и он будет обрабатываться параллельно.

Я предположил, что у вас есть функция с такой сигнатурой: Function FetchStatus(MachineName As String, ServiceName As String) As String .

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

1. Кажется, это работает, но показывает ли последняя часть по-прежнему правильный статус для правильной строки? Когда я пытаюсь запустить код со списком из 10 строк (серверов), я по-прежнему использую dgvrow. Ячейки (2).Значение заполняется последовательно сверху вниз. Я ожидаю, что все ячейки (2) в каждых 10 строках получат значение одновременно.

2. @Ruben_PH — При условии, что порядок строк не меняется во время вычисления.

3. @Ruben_PH — Если есть какие-либо сомнения по поводу порядка изменения строк, тогда просто передайте фактическую строку в анонимный объект прямо через вычисления.

4. Есть ли способ, чтобы ячейка (2) заполнялась одновременно? Запустив это в списке примерно из 10 серверов, я вижу, что ячейка (2) по-прежнему заполняется сверху донизу.

5. Я добавил еще несколько деталей по вопросу для ожидаемого результата