OPERA?ÃO ENTRE THREADS INV?LIDA - DELEGATE?

GGERMINIANI 30/05/2014 09:30:32
#438623
Pessoal,
Bom dia.

Gostaria muito da ajuda de vocês... pesquisei na internet e tudo mais, porem não achei conteúdo que me ajudasse.

Estou com problema, que na execução de uma thread preciso atualizar um DGV, porem dá o erro: [Ô][txt-color=#e80000]Operação entre threads inválida[/txt-color][Ô]

Já verifiquei em minhas pesquisas que é necessário invocar o método delegate, porem não estou conseguindo fazê-lo...

Bom, passo a baixo a estrutura do meu código (ele é pequeno, usado para importar arquivos XMLs ao DGV).

-------------------------------------

Foi criado um BackgroundWorker no form, pois desejo que quando executar a rotina de importação no DGV (são bastantes arquivos), haja a possibilidade do usuário querer cancelar a importação.

Ao clicar no botão executa o BackgroundWorker.RunWorkerAsync()

  
Private Sub BackgroundWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker.DoWork
BarraProgresso.Maximum = ArquivosListBox.Items.Count [ô]tem uma barra de progresso no form para demonstrar a situação
VerProgresso(sender, e)
End Sub


Private Sub VerProgresso(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs)

Dim contador As Integer = 0

For Each i In ArquivosListBox.Items [ô]Aqui contém os arquivos a serem importado, ou seja, a cada item será chamado a sub ImportarArquivos( i.ToString igual ao Caminho do Arquivo)

If BackgroundWorker.CancellationPending = True Then
e.Cancel = True
Exit Sub
End If

ImportarArquivos(i.ToString)
contador += 1
BarraProgresso.Value = contador

Next

End Sub


Private Sub ImportarArquivos(Caminho As String) [ô]Aqui atualiza o DGV
[ô][...]
DGV.Rows.Add(N_NOTA, TIPO, FormatDateTime(Convert.ToDateTime(EMISSAO), DateFormat.ShortDate), CODIGO, DESCRI, NCM, CFOP, UNID, FormatNumber(Replace(QNTDE, [Ô].[Ô], [Ô],[Ô]), 6), FormatNumber(Replace(UNIT, [Ô].[Ô], [Ô],[Ô]), 6), FormatNumber(Replace(TOTAL, [Ô].[Ô], [Ô],[Ô]), 6), CHAVE) [ô]Aqui acontece o erro!

End Sub



Private Sub BackgroundWorker_RunWorkerCompleted(sender As System.Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker.RunWorkerCompleted [ô]Finalizando a Thread

BarraProgresso.Value = BarraProgresso.Minimum
CancelarButton.Enabled = True


If Not e.Cancelled = True Then
MsgBox([Ô]Operação finalizada[Ô])
Else
MsgBox([Ô]Operação cancelada[Ô])
End If

End Sub


Private Sub CancelarButton_Click(sender As System.Object, e As System.EventArgs) Handles CancelarButton.Click [ô]Cancelando a Thread

If BackgroundWorker.IsBusy = True Then [ô]IsBusy (Está funcionando/ em execução)?
BackgroundWorker.CancelAsync()
End If

End Sub



Bom pessoal... basicamente este é todo o código do meu form. Gostaria de saber... como invoco o VerProgresso que consequentemente invoca o ImportarArquivos, o qual atualiza o DGV onde ocorre o erro?

Caso pareça confuso, tento explicar novamente.

Agradeço muito desde já.
KERPLUNK 30/05/2014 10:03:34
#438626
Vamos ver se entendi. Você tem uma lista de arquivos que devem ter um certo processamento. Você quer uma thread separada para cada processamento com um indicador de progresso. Isso?
GGERMINIANI 30/05/2014 10:18:20
#438627
Não seria separada....

Executarei a thread chamando a sub VerProgresso.

Em VerProgressp:
  • Será lido cada item de uma lista, e então executada uma outra sub que adicionará uma linha no DGV
  • Será informado a minha barra de status o total de items que já foram lidos
  • A cada For Each lido, por ser um thread, haverá a possibilidade do usuário clicar num botão CANCELAR do form, e caso na leitura atual do for each (ou na próxima), conste que o botão CANCELAR foi acionado, é cancelada a execução da thread VerProgresso

    Resumindo...
    0>IMPORTAR.click
    1>BackgroundWorker.RunWorkerAsync()
    2>VerProgresso
    2.1>Lê cada item da lista e executa a sub CarregaArquivo (a qual atualiza o DGV
    2.2>Para cada leitura dos itens da lista, é verificado se o botão cancelar foi acionado, se sim, cancela essa execução

    Em 2.1 está o problema, pois pelo que pesquisei, não se pode atualizar um objeto em execução de uma thread.

    Consegui explicar?

    Grato.
  • KERPLUNK 30/05/2014 11:46:04
    #438630
    O que você quer:
    Uma thread separada do form, atualizando um componente do form(uma barra de progresso) e um componente do form que atue na thread que está separada do form. Ou seja, é bem difícil de se fazer, mas não impossível. E não vai ter nenhum ganho de performance com isso.
    OCELOT 30/05/2014 14:26:53
    #438633
    Você não deve usar threads se precisa atualizar a tela na operação que quer fazer nela.

    Você só deve usar threads se o que quer fazer não depende de atualizar a tela, pois sempre que precisa atualizar a tela você vai precisar fazer um Invoke e ele vai fazer o código (que foi passado para o invoke) rodar na thread do form, o que se for feito com muita frequência pode acabar deixando o processo mais lento que fazer tudo direto na thread do form.

    O que você poderia fazer é carregar um List(T) ou um DataSet e depois dele carregado por completo você mostrar estes dados no DataGrid
    GGERMINIANI 30/05/2014 15:41:38
    #438634
    Ocelot,
    Boa tarde.

    Acredito que entendi o que você disse e a solução que me propôs. Mas também há a necessidade de deixar o form [Ô]livre[Ô] caso o usuário pretenda cancelar o processo, visto que pode chegar a 20.000 arquivos a serem importados.

    Dúvida: até onde sei, para poder interromper um processo em andamento, somente caso ele esteja sendo executado por uma thread, pois caso não esteja, só posso executar outro comando após o processo finalizar.... ou não?

    Grato.
    KERPLUNK 30/05/2014 16:55:26
    #438636
    Não. Você pode estabelecer uma variável e consultá-la a cada interação. Se o valor estiver false para o loop usando return
    OCELOT 30/05/2014 17:09:36
    #438637
    Completando o que o Kerplunk disse, seria possível fazer assim se você usar o Application.DoEvents no loop, sem precisar de Threads, a menos que tenha algo que você faça no loop que seja muito demorado isso pode funcionar.
    GGERMINIANI 30/05/2014 17:45:43
    #438640
    Senhores,
    Achei uma solução!

    Não sei se é exatamente o que vcs me recomendaram, mas fiz da seguinte forma:

    Continua sendo uma thread...

    0>IMPORTAR.click
    1>BackgroundWorker.RunWorkerAsync()
    2>VerProgresso
    2.1>Lê cada item da lista e executa a sub CarregaArquivo (a qual atualiza o DGV - [txt-color=#007100]aqui substitui a atualização direta no DGV, por uma varíavel List of de uma classe que criei com propertys referentes[/txt-color])
    2.2>Para cada leitura dos itens da lista, é verificado se o botão cancelar foi acionado, se sim, cancela essa execução
    2.3> ativei o Reports do BackgroundWorder, e pedi para que ele retornasse o status para o progress bar...

    Ficou assim o código:


    ACIONANDO A THREAD
        Private Sub BackgroundWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker.DoWork

    VerProgresso(sender, e)

    End Sub


    EXECUÇÃO DA SUB...

    Private Sub VerProgresso(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs)

    Dim contador As Integer = 0

    For Each i In ArquivosListBox.Items

    If BackgroundWorker.CancellationPending = True Then
    e.Cancel = True
    Exit Sub
    End If


    BackgroundWorker.ReportProgress(contador) [ô]aqui informo o contador, que será referência de quantos arquivos foram lidos
    ImportarArquivos(i.ToString)
    contador += 1
    Threading.Thread.Sleep(50)
    Next

    End Sub


    AQUI O PROCESSO DE ATUALIZAR O DGV FOI SUBSTITUÍDO POR PREENCHIMENTO DE UMA VARIÁVEL DE OUTRA CLASSE...

    Private Sub ImportarArquivos(Caminho As String)

    [...]
    Dim lNFE As New NFExml
    lNFE.CODIGO = CODIGO.Trim()
    lNFE.DESCRI = DESCRI.Trim()
    lNFE.NCM = NCM.Trim()
    lNFE.CFOP = CFOP.Trim()
    lNFE.UNID = UNID.Trim()
    lNFE.QNTDE = QNTDE.Trim()
    lNFE.UNIT = UNIT.Trim()
    lNFE.TOTAL = TOTAL.Trim()
    lNFE.EMISSAO = EMISSAO.Trim()
    lNFE.CHAVE = CHAVE.Trim()
    lNFE.N_NOTA = N_NOTA.Trim()
    lNFE.TIPO = TIPO.Trim()
    RetornoX.Add(lNFE)

    [...]
    End Sub


    INFORMAÇÃO DO PROGRESSO NA PROGRESS BAR

    Private Sub BackgroundWorker_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker.ProgressChanged
    BarraProgresso.Value = e.ProgressPercentage
    End Sub


    Quando finaliza a thread, aí sim o DGV é preenchido

    Private Sub BackgroundWorker_RunWorkerCompleted(sender As System.Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker.RunWorkerCompleted

    BarraProgresso.Value = BarraProgresso.Minimum
    NFEDataGridView.Visible = True
    AguardeGroupBox.Visible = False

    NFEDataGridView.DataSource = RetornoX

    If Not e.Cancelled = True Then
    MsgBox([Ô]Operação finalizada[Ô])
    Else
    MsgBox([Ô]Operação cancelada[Ô])
    End If

    End Sub


    Caso seja clicado no botão CANCELAR

    Private Sub CancelarButton_Click(sender As System.Object, e As System.EventArgs) Handles CancelarButton.Click

    If BackgroundWorker.IsBusy = True Then [ô]IsBusy (Está funcionando/ em execução)?
    BackgroundWorker.CancelAsync()
    End If

    End Sub



    Enfim, não sei se é o que vcs me disseram, ou se é o mais correto.... mas é uma alternativa!

    Obrigado à todos!!!!
    Tópico encerrado , respostas não são mais permitidas