O encadeamento de chamada não pode acessar esse objeto porque um encadeamento diferente o possui

341

Meu código é como abaixo

public CountryStandards()
{
    InitializeComponent();
    try
    {
        FillPageControls();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Country Standards", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

/// <summary>
/// Fills the page controls.
/// </summary>
private void FillPageControls()
{
    popUpProgressBar.IsOpen = true;
    lblProgress.Content = "Loading. Please wait...";
    progress.IsIndeterminate = true;
    worker = new BackgroundWorker();
    worker.DoWork += new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
    worker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(worker_ProgressChanged);
    worker.WorkerReportsProgress = true;
    worker.WorkerSupportsCancellation = true;
    worker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
    worker.RunWorkerAsync();                    
}

private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
    GetGridData(null, 0); // filling grid
}

private void worker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
    progress.Value = e.ProgressPercentage;
}

private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
    worker = null;
    popUpProgressBar.IsOpen = false;
    //filling Region dropdown
    Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
    objUDMCountryStandards.Operation = "SELECT_REGION";
    DataSet dsRegionStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
    if (!StandardsDefault.IsNullOrEmptyDataTable(dsRegionStandards, 0))
        StandardsDefault.FillComboBox(cmbRegion, dsRegionStandards.Tables[0], "Region", "RegionId");

    //filling Currency dropdown
    objUDMCountryStandards = new Standards.UDMCountryStandards();
    objUDMCountryStandards.Operation = "SELECT_CURRENCY";
    DataSet dsCurrencyStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
    if (!StandardsDefault.IsNullOrEmptyDataTable(dsCurrencyStandards, 0))
        StandardsDefault.FillComboBox(cmbCurrency, dsCurrencyStandards.Tables[0], "CurrencyName", "CurrencyId");

    if (Users.UserRole != "Admin")
        btnSave.IsEnabled = false;

}

/// <summary>
/// Gets the grid data.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="pageIndex">Index of the page.( used in case of paging)   </pamam>
private void GetGridData(object sender, int pageIndex)
{
    Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
    objUDMCountryStandards.Operation = "SELECT";
    objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;
    DataSet dsCountryStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
    if (!StandardsDefault.IsNullOrEmptyDataTable(dsCountryStandards, 0) && (chkbxMarketsSearch.IsChecked == true || chkbxBudgetsSearch.IsChecked == true || chkbxProgramsSearch.IsChecked == true))
    {
        DataTable objDataTable = StandardsDefault.FilterDatatableForModules(dsCountryStandards.Tables[0], "Country", chkbxMarketsSearch, chkbxBudgetsSearch, chkbxProgramsSearch);
        dgCountryList.ItemsSource = objDataTable.DefaultView;
    }
    else
    {
        MessageBox.Show("No Records Found", "Country Standards", MessageBoxButton.OK, MessageBoxImage.Information);
        btnClear_Click(null, null);
    }
}

A etapa objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;em obter dados da grade gera exceção

O encadeamento de chamada não pode acessar esse objeto porque ele possui um encadeamento diferente.

O que há de errado aqui?

Kuntady Nithesh
fonte

Respostas:

697

Esse é um problema comum nas pessoas que estão começando. Sempre que você atualiza seus elementos da interface do usuário a partir de um thread diferente do thread principal, é necessário usar:

this.Dispatcher.Invoke(() =>
{
    ...// your code here.
});

Você também pode usar control.Dispatcher.CheckAccess()para verificar se o segmento atual possui o controle. Se possuir, seu código parecerá normal. Caso contrário, use o padrão acima.

Candide
fonte
3
Eu tenho o mesmo problema que OP; Meu problema agora é que o evento causa agora um estouro de pilha. : \
Malavos
2
Voltei ao meu projeto antigo e resolvi isso. Além disso, eu tinha esquecido de marcar com +1. Este método funciona muito bem! Ele melhora o tempo de carregamento do aplicativo em 10 segundos ou mais, apenas usando threads para carregar nossos recursos localizados. Felicidades!
Malavos
4
Se não estou errado, você nem consegue ler um objeto de interface do usuário a partir de um thread que não é proprietário; me surpreendeu um pouco.
Elliot
32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);para obter o despachante se não no segmento interface do usuário de acordo com esta resposta
JumpingJezza
2
+1. Ha! Eu usei isso em alguns hackers do WPF para manter as coisas dissociadas. Eu estava em um contexto estático, então não podia usar this.Dispatcher.Invoke.... em vez disso ... myControl.Dispatcher.Invoke:) Eu precisava retornar um objeto de volta, então o fiz myControlDispatcher.Invoke<object>(() => myControl.DataContext);
C. Tewalt
52

Outro bom uso Dispatcher.Invokeé para atualizar imediatamente a interface do usuário em uma função que executa outras tarefas:

// Force WPF to render UI changes immediately with this magic line of code...
Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle);

Eu uso isso para atualizar o texto do botão para " Processando ... " e desativá-lo ao fazer WebClientsolicitações.

computerGuyCJ
fonte
4
Esta resposta está sendo discutida no Meta. meta.stackoverflow.com/questions/361844/…
JDB ainda se lembra de Monica
Isso parou meu controle de obter dados da internet?
Waseem Ahmad Naeem
41

Para adicionar meus 2 centavos, a exceção pode ocorrer mesmo se você chamar seu código System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
O ponto é que você tem que chamar Invoke()o Dispatcherdo controle que você está tentando acessar , o que em alguns casos pode não ser o mesmo que System.Windows.Threading.Dispatcher.CurrentDispatcher. Então, em vez disso, você deve usar YourControl.Dispatcher.Invoke()para estar seguro. Eu estava batendo minha cabeça por algumas horas antes de perceber isso.

Atualizar

Para futuros leitores, parece que isso mudou nas versões mais recentes do .NET (4.0 e superior). Agora você não precisa mais se preocupar com o expedidor correto ao atualizar as propriedades de backup da interface do usuário na sua VM. O mecanismo WPF empacotará as chamadas de thread cruzado no thread de interface do usuário correto. Veja mais detalhes aqui . Obrigado a @aaronburro pela informação e link. Você também pode ler nossa conversa abaixo nos comentários.

ponto Net
fonte
4
@ l33t: O WPF suporta vários threads da interface do usuário em um aplicativo, cada um com o seu Dispatcher. Nesses casos (que são reconhecidamente raros), chamar Control.Dispatcheré a abordagem segura. Para referência, você pode ver este artigo e também esta postagem do SO (principalmente a resposta do Lula Molusco).
dotNET 27/09/16
11
Curiosamente, eu estava enfrentando essa mesma exceção quando pesquisei e cheguei nesta página e, como a maioria de nós, tentou a resposta mais votada, o que não resolveu meu problema na época. Descobri esse motivo e o publiquei aqui para desenvolvedores de mesmo nível.
dotNET 27/09/16
11
@ l33t, se você estiver usando o MVVM corretamente, não deverá ser um problema. A visualização necessariamente sabe qual Dispatcher está usando, enquanto os modelos e modelos de exibição não sabem nada sobre controles e não precisam saber sobre controles.
aaronburro
11
@aaronburro: O problema é que a VM pode querer iniciar ações em threads alternativos (por exemplo, tarefas, ações baseadas em timer, consultas paralelas) e, à medida que a operação avança, pode querer atualizar a interface do usuário (por meio de RaisePropertyChanged etc), que por sua vez tenta acessar um controle de interface do usuário a partir de thread que não seja da interface do usuário e, portanto, resultar nessa exceção. Não conheço uma abordagem MVVM correta que resolva esse problema.
dotNET 24/02
11
O mecanismo de ligação do WPF empacota automaticamente os eventos de alteração de propriedade no Dispatcher correto. É por isso que a VM não precisa conhecer o Dispatcher; tudo o que precisa fazer é apenas aumentar os eventos alterados de propriedade. A ligação WinForms é uma história diferente.
aaronburro
34

Se você encontrar esse problema e os Controles da UI foram criados em um thread de trabalho separado ao trabalhar com BitmapSourceou ImageSourceno WPF, chame o Freeze()método primeiro antes de passar o BitmapSourceou ImageSourcecomo parâmetro para qualquer método. O uso Application.Current.Dispatcher.Invoke()não funciona nesses casos

juFo
fonte
24
Ah, nada como um bom e velho truque vago e misterioso para resolver algo que ninguém entende.
Edwin
2
Eu adoraria mais informações sobre por que isso funciona e como eu poderia ter descoberto isso sozinho.
Xavier Shay
25

isso aconteceu comigo porque eu tentei access UIno componenteanother thread insted of UI thread

como isso

private void button_Click(object sender, RoutedEventArgs e)
{
    new Thread(SyncProcces).Start();
}

private void SyncProcces()
{
    string val1 = null, val2 = null;
    //here is the problem 
    val1 = textBox1.Text;//access UI in another thread
    val2 = textBox2.Text;//access UI in another thread
    localStore = new LocalStore(val1);
    remoteStore = new RemoteStore(val2);
}

Para resolver esse problema, envolva qualquer chamada de interface do usuário dentro do que Candide mencionou acima em sua resposta

private void SyncProcces()
{
    string val1 = null, val2 = null;
    this.Dispatcher.Invoke((Action)(() =>
    {//this refer to form in WPF application 
        val1 = textBox.Text;
        val2 = textBox_Copy.Text;
    }));
    localStore = new LocalStore(val1);
    remoteStore = new RemoteStore(val2 );
}
Basheer AL-MOMANI
fonte
11
Voto positivo, porque não é uma resposta duplicada ou plágio, mas fornece um bom exemplo de que faltavam outras respostas, dando crédito ao que foi postado anteriormente.
Panzercrisis 02/09
O voto positivo é para uma resposta clara. Embora o mesmo tenha sido escrito por outros, mas isso deixa claro para quem está preso.
NishantM #
15

Por alguma razão, a resposta de Candide não foi boa. Foi útil, porém, pois me levou a encontrar isso, que funcionou perfeitamente:

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
   //your code here...
}));
Sarah
fonte
É possível que você não tenha chamado da classe do formulário. Ou você pode pegar uma referência à Janela ou provavelmente pode usar o que sugeriu.
Simone
4
Se funcionou para você, não foi necessário usá-lo em primeiro lugar. System.Windows.Threading.Dispatcher.CurrentDispatcheré o expedidor para o encadeamento atual . Isso significa que, se você estiver em um encadeamento em segundo plano, não será o expedidor do encadeamento da interface do usuário. Para acessar o distribuidor da thread da interface do usuário, use System.Windows.Application.Current.Dispatcher.
13

Você precisa atualizar para a interface do usuário, então use

Dispatcher.BeginInvoke(new Action(() => {GetGridData(null, 0)})); 
VikramBose
fonte
4

Isso funciona para mim.

new Thread(() =>
        {

        Thread.CurrentThread.IsBackground = false;
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate {

          //Your Code here.

        }, null);
        }).Start();
nPcomp
fonte
3

Também descobri que System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()nem sempre é despachante do controle de destino, assim como o dotNet escreveu em sua resposta. Como não tinha acesso ao despachante do controle, usei Application.Current.Dispatchere resolvi o problema.

Paulus Limma
fonte
2

O problema é que você está chamando GetGridDatade um thread em segundo plano. Este método acessa vários controles WPF que estão vinculados ao thread principal. Qualquer tentativa de acessá-los a partir de um encadeamento em segundo plano levará a esse erro.

Para voltar ao segmento correto, você deve usar SynchronizationContext.Current.Post. No entanto, nesse caso em particular, parece que a maioria do trabalho que você está fazendo é baseada na interface do usuário. Portanto, você criaria um encadeamento em segundo plano apenas para retornar imediatamente ao encadeamento da interface do usuário e fazer algum trabalho. Você precisa refatorar um pouco o código para que ele possa fazer o trabalho caro no encadeamento em segundo plano e depois postar os novos dados no encadeamento da interface do usuário posteriormente

JaredPar
fonte
2

Como mencionado aqui , Dispatcher.Invokepode congelar a interface do usuário. Deve usar em seu Dispatcher.BeginInvokelugar.

Aqui está uma classe de extensão útil para simplificar a chamada de verificação e chamada do expedidor.

Exemplo de uso: (chamada da janela WPF)

this Dispatcher.InvokeIfRequired(new Action(() =>
{
    logTextbox.AppendText(message);
    logTextbox.ScrollToEnd();
}));

Classe de extensão:

using System;
using System.Windows.Threading;

namespace WpfUtility
{
    public static class DispatcherExtension
    {
        public static void InvokeIfRequired(this Dispatcher dispatcher, Action action)
        {
            if (dispatcher == null)
            {
                return;
            }
            if (!dispatcher.CheckAccess())
            {
                dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
                return;
            }
            action();
        }
    }
}
Jeson Martajaya
fonte
0

Além disso, outra solução é garantir que seus controles sejam criados no thread da interface do usuário, não por um thread de trabalho em segundo plano, por exemplo.

FindOutIslamNow
fonte
0

Continuei recebendo o erro ao adicionar caixas de combinação em cascata ao meu aplicativo WPF e resolvi o erro usando esta API:

    using System.Windows.Data;

    private readonly object _lock = new object();
    private CustomObservableCollection<string> _myUiBoundProperty;
    public CustomObservableCollection<string> MyUiBoundProperty
    {
        get { return _myUiBoundProperty; }
        set
        {
            if (value == _myUiBoundProperty) return;
            _myUiBoundProperty = value;
            NotifyPropertyChanged(nameof(MyUiBoundProperty));
        }
    }

    public MyViewModelCtor(INavigationService navigationService) 
    {
       // Other code...
       BindingOperations.EnableCollectionSynchronization(AvailableDefectSubCategories, _lock );

    }

Para obter detalhes, consulte https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization);k(TargetFrameworkMoniker-.NETFramework,Version % 3Dv4.7); k (DevLang-csharp) & rd = true

user8128167
fonte