Pergunta

Eu tenho um UserControl simples para paginação banco de dados, que usa um controlador para executar as chamadas DAL reais. Eu uso um BackgroundWorker para executar o trabalho pesado, e sobre o evento OnWorkCompleted I reativar alguns botões, alterar uma propriedade TextBox.Text e levantar um evento para o formulário pai.

Form A segura minha UserControl. Quando eu clicar em algum botão que abre o formulário B, mesmo que eu não faça nada "lá" e apenas fechá-lo, e tentar trazer na próxima página do meu banco de dados, o OnWorkCompleted é chamado no segmento de trabalho (e não meu thread principal), e lança uma exceção entre segmentos.

No momento eu adicionei um cheque de InvokeRequired no manipulador de lá, mas não é o ponto inteiro de OnWorkCompleted deve ser chamado no segmento Main? Por que não seria funcionar como esperado?

EDIT:

Eu consegui reduzir o problema ao ArcGIS e BackgroundWorker. Eu tenho a seguinte solução wich adiciona um comando para ArcMap, que abre uma Form1 simples com dois botões.

O primeiro botão executa um BackgroundWorker que dorme por 500ms e atualiza um contador. No método RunWorkerCompleted ele verifica InvokeRequired, e atualiza o título para mostrar whethever o método foi originalmente execução dentro do segmento principal ou o segmento de trabalho. O segundo botão só abre Form2, que não contém nada.

Em primeiro lugar, todas as chamadas para RunWorkerCompletedare são feitas dentro do segmento principal (como esperado - isso é o ponto whold do método RunWorkerComplete, pelo menos pelo que eu entendo do MSDN em BackgroundWorker)

Depois de abrir e fechar Form2, o RunWorkerCompleted está sempre sendo chamado no segmento de trabalho. Quero acrescentar que eu posso simplesmente deixar esta solução para o problema como é (cheque de InvokeRequired no método RunWorkerCompleted), mas eu quero entender por que está acontecendo contra as minhas expectativas. No meu código "real" Eu gostaria de saber sempre que o método RunWorkerCompleted está sendo chamado no segmento principal.

Eu consegui fixar o ponto o problema sob o comando form.Show(); na minha BackgroundTesterBtn - se eu usar ShowDialog() em vez disso, eu recebo nenhum problema (RunWorkerCompleted sempre é executado no thread principal). I precisa usar Show() no meu projeto ArcMap, de modo que o usuário não será vinculada ao formulário.

Eu também tentei reproduzir o bug em um projeto WinForms normal. Eu adicionei um projeto simples que só abre a primeira forma sem ArcMap, mas nesse caso eu não poderia reproduzir o bug - RAN RunWorkerCompleted no segmento principal, se eu usei Show() ou ShowDialog(), antes e depois Form2 abertura. Eu tentei adicionar uma terceira forma de agir como um formulário principal antes do meu Form1, mas não mudar o resultado.

Aqui é o meu sln simples (VS2005sp1) - requer

ESRI.ArcGIS.ADF (9.2.4.1420)

ESRI.ArcGIS.ArcMapUI (9.2.3.1380)

ESRI.ArcGIS.SystemUI (9.2.3.1380)

Foi útil?

Solução

Parece que um erro:

http://connect.microsoft.com/VisualStudio/feedback /ViewFeedback.aspx?FeedbackID=116930

http://thedatafarm.com/devlifeblog/archive/2005 /12/21/39532.aspx

Então, eu sugiro usar a prova de bala (pseudocódigo):

if(control.InvokeRequired)
  control.Invoke(Action);
else
  Action()

Outras dicas

Não é todo o ponto de OnWorkCompleted deve ser chamado no segmento Main? Por que não seria funcionar como esperado?

Não, não é.
Você não pode simplesmente ir a correr qualquer coisa velha em qualquer segmento de idade. Tópicos não são objetos educado que você pode simplesmente dizer "executar isso, por favor".

Um modelo melhor mental de um fio é um trem de carga. Uma vez que ele vai, ele está fora, por si própria pista. Você não pode mudá-lo do curso ou pará-lo. Se você quer influenciá-lo, você tem que esperar até que ele chegue à próxima estação ferroviária (por exemplo: tem que verificar manualmente para alguns eventos), ou descarrilar (Thread.Abort e CrossThread exceções têm muito as mesmas consequências que descarrilar um trem. .. cuidado!).

WinForms controles tipo de apoiar este comportamento (Eles têm Control.BeginInvoke que permite executar qualquer função no segmento interface do usuário), mas isso só funciona porque eles têm um gancho especial para a bomba janelas mensagem de interface do usuário e escrever alguns manipuladores especiais. Para ir com a analogia acima, os seus controlos de trem na estação de e procura por novas direções periodicamente, e você pode usar esse recurso para postar suas próprias direções.

O BackgroundWorker é projetado para ser de uso geral (que não pode ser ligada à GUI janelas) para que ele não pode usar as janelas Control.BeginInvoke características. Ele tem que assumir que o seu segmento principal é um 'trem' imparável fazer a sua própria coisa, então o evento concluído tem de correr no segmento de trabalho ou não em todos.

No entanto, como você está usando winforms, no seu manipulador OnWorkCompleted, você pode obter a janela para executar outro callback usando a funcionalidade BeginInvoke eu mencionei acima. Como esta:

// Assume we're running in a windows forms button click so we have access to the 
// form object in the "this" variable.
void OnButton_Click(object sender, EventArgs e )
    var b = new BackgroundWorker();
    b.DoWork += ... blah blah

    // attach an anonymous function to the completed event.
    // when this function fires in the worker thread, it will ask the form (this)
    // to execute the WorkCompleteCallback on the UI thread.
    // when the form has some spare time, it will run your function, and 
    // you can do all the stuff that you want
    b.RunWorkerCompleted += (s, e) { this.BeginInvoke(WorkCompleteCallback); }
    b.RunWorkerAsync(); // GO!
}

void WorkCompleteCallback()
{
    Button.Enabled = false;
    //other stuff that only works in the UI thread
}

Além disso, não se esqueça esta:

O seu manipulador de eventos RunWorkerCompleted deve sempre verificar o erro e propriedades canceladas antes de acessar a propriedade resultado. Se uma exceção foi levantada ou se a operação foi cancelada, acessando a propriedade Resultado levanta uma exceção.

As verificações BackgroundWorker se a instância delegado, aponta para uma classe que suporta o ISynchronizeInvoke interface. Sua camada DAL provavelmente não implementar essa interface. Normalmente, você usaria o BackgroundWorker em um Form, que suporta essa interface.

No caso de você querer usar o BackgroundWorker da camada DAL e deseja atualizar a interface do usuário a partir daí, você tem três opções:

  • você ficasse chamando o método Invoke
  • implementar o ISynchronizeInvoke interface na classe DAL, e redirecionar as chamadas manualmente (é apenas três métodos e uma propriedade)
  • antes de invocar o BackgroundWorker (assim, no thread UI), para chamar SynchronizationContext.Current e para salvar a instância conteúdo em uma variável de instância. O SynchronizationContext, então, dar-lhe o método Send, que vai fazer exatamente o que Invoke faz.

A melhor abordagem para questões evitar com cruz-threading em GUI é uso SynchronizationContext .

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top