Le thread appelant ne peut pas accéder à cet objet car un thread différent le possède

342

Mon code est comme ci-dessous

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);
    }
}

L'étape objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;d'obtention des données de grille lève une exception

Le thread appelant ne peut pas accéder à cet objet car un thread différent le possède.

Qu'est-ce qui ne va pas ici?

Kuntady Nithesh
la source

Réponses:

699

Il s'agit d'un problème courant chez les personnes qui commencent. Chaque fois que vous mettez à jour vos éléments d'interface utilisateur à partir d'un thread autre que le thread principal, vous devez utiliser:

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

Vous pouvez également utiliser control.Dispatcher.CheckAccess()pour vérifier si le thread actuel possède le contrôle. S'il le possède, votre code semble normal. Sinon, utilisez le modèle ci-dessus.

Candide
la source
3
J'ai le même problème que OP; Mon problème est maintenant que l'événement provoque maintenant un débordement de pile. : \
Malavos
2
Je suis retourné à mon ancien projet et j'ai résolu cela. De plus, j'avais oublié d'ajouter +1 à cela. Cette méthode fonctionne plutôt bien! Il améliore le temps de chargement de mon application sur 10 secondes ou plus, simplement en utilisant des threads pour charger nos ressources localisées. À votre santé!
Malavos
4
Si je ne me trompe pas, vous ne pouvez même pas lire un objet d'interface utilisateur à partir d'un thread non propriétaire; m'a un peu surpris.
Elliot
32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);pour obtenir le répartiteur sinon sur le thread d'interface utilisateur selon cette réponse
JumpingJezza
2
+1. Ha! Je l'ai utilisé pour certains piratages WPF pour garder les choses découplées. J'étais dans un contexte statique donc je ne pouvais pas utiliser this.Dispatcher.Invoke.... à la place ... myControl.Dispatcher.Invoke:) Je devais retourner un objet alors je l'ai fait myControlDispatcher.Invoke<object>(() => myControl.DataContext);
C.Tewalt
53

Une autre bonne utilisation pour Dispatcher.Invokeest de mettre à jour immédiatement l'interface utilisateur dans une fonction qui effectue d'autres tâches:

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

Je l'utilise pour mettre à jour le texte du bouton en " Traitement ... " et le désactiver lors de la WebClientdemande.

computerGuyCJ
la source
4
Cette réponse est en cours de discussion sur Meta. meta.stackoverflow.com/questions/361844/…
JDB se souvient encore de Monica le
Cela a empêché mon contrôle d'obtenir des données sur Internet?
Waseem Ahmad Naeem
41

Pour ajouter mes 2 cents, l'exception peut se produire même si vous appelez votre code via System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
Le point est que vous devez appeler Invoke()de Dispatcherla commande que vous tentez d'accéder , dans certains cas , ne peut pas être le même que System.Windows.Threading.Dispatcher.CurrentDispatcher. Donc, à la place, vous devez utiliser YourControl.Dispatcher.Invoke()pour être en sécurité. Je me cognais la tête pendant quelques heures avant de m'en rendre compte.

Mettre à jour

Pour les futurs lecteurs, il semble que cela ait changé dans les nouvelles versions de .NET (4.0 et supérieures). Désormais, vous n'avez plus à vous soucier du bon répartiteur lors de la mise à jour des propriétés de sauvegarde de l'interface utilisateur dans votre machine virtuelle. Le moteur WPF va rassembler les appels inter-threads sur le thread d'interface utilisateur correct. Voir plus de détails ici . Merci à @aaronburro pour l'info et le lien. Vous pouvez également lire notre conversation ci-dessous dans les commentaires.

point net
la source
4
@ l33t: WPF prend en charge plusieurs threads d'interface utilisateur dans une seule application, chacune ayant la sienne Dispatcher. Dans ces cas (qui sont certes rares), appeler Control.Dispatcherest l'approche sûre. Pour référence, vous pouvez voir cet article ainsi que cet article SO (en particulier la réponse de Squidward).
dotNET
1
Fait intéressant, je faisais face à cette exception même lorsque j'ai googlé et atterri sur cette page et, comme la plupart d'entre nous, j'ai essayé la réponse la plus votée, ce qui n'a pas résolu mon problème à l'époque. J'ai ensuite découvert cette raison et l'ai publiée ici pour les développeurs pairs.
dotNET
1
@ l33t, si vous utilisez correctement MVVM, cela ne devrait pas poser de problème. La vue sait nécessairement quel Dispatcher elle utilise, tandis que les ViewModels et les modèles ne connaissent rien aux contrôles et n'ont pas besoin de connaître les contrôles.
aaronburro
1
@aaronburro: Le problème est que la machine virtuelle peut vouloir lancer des actions sur des threads alternatifs (par exemple, tâches, actions basées sur le minuteur, requêtes parallèles), et que l'opération progresse, peut souhaiter mettre à jour l'interface utilisateur (via RaisePropertyChanged, etc.), qui à son tour essaiera pour accéder à un contrôle d'interface utilisateur à partir d'un thread non-interface utilisateur et entraîner ainsi cette exception. Je ne connais pas une approche MVVM correcte qui résoudrait ce problème.
dotNET
1
Le moteur de liaison WPF rassemble automatiquement les événements de modification de propriété vers le répartiteur approprié. C'est pourquoi VM n'a pas besoin de connaître Dispatcher; tout ce qu'il a à faire est simplement de déclencher des événements de modification de propriété. La liaison WinForms est une autre histoire.
aaronburro
34

Si vous rencontrez ce problème et que des contrôles d'interface utilisateur ont été créés sur un thread de travail distinct lorsque vous travaillez avec WPF BitmapSourceou ImageSourcedans WPF, appelez d' Freeze()abord la méthode avant de passer le BitmapSourceou ImageSourcecomme paramètre à une méthode. L'utilisation Application.Current.Dispatcher.Invoke()ne fonctionne pas dans de tels cas

juFo
la source
24
Ah, rien de tel qu'un bon vieux truc vague et mystérieux pour résoudre quelque chose que personne ne comprend.
Edwin
2
J'aimerais plus d'informations sur la raison pour laquelle cela fonctionne et comment j'aurais pu le comprendre moi-même.
Xavier Shay
25

ce qui est arrivé avec moi parce que j'essayé de access UIdans le composantanother thread insted of UI thread

comme ça

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);
}

pour résoudre ce problème, enveloppez tout appel ui dans ce que Candide a mentionné ci-dessus dans sa réponse

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
la source
1
Voté, car il ne s'agit pas d' une réponse en double ou plagiaire, mais il fournit plutôt un bon exemple que d'autres réponses manquaient, tout en donnant du crédit pour ce qui a été publié plus tôt.
Panzercrisis
Upvote est pour une réponse claire. Bien que la même chose ait été écrite par d'autres, mais cela le rend clair pour quiconque est coincé.
NishantM
15

Pour une raison quelconque, la réponse de Candide n'a pas été construite. Cela a été utile, cependant, car cela m'a conduit à trouver cela, qui fonctionnait parfaitement:

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
   //your code here...
}));
Sarah
la source
Il est possible que vous n'ayez pas appelé depuis la classe du formulaire. Soit vous pouvez saisir une référence à la fenêtre, soit vous pouvez probablement utiliser ce que vous avez suggéré.
Simone
4
Si cela fonctionnait pour vous, il n'était pas nécessaire de l'utiliser en premier lieu. System.Windows.Threading.Dispatcher.CurrentDispatcherest le répartiteur du thread actuel . Cela signifie que si vous êtes sur un thread d'arrière-plan, ce ne sera pas le répartiteur du thread d'interface utilisateur. Pour accéder au répartiteur du thread d'interface utilisateur, utilisez System.Windows.Application.Current.Dispatcher.
13

Vous devez mettre à jour l'interface utilisateur, alors utilisez

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

Cela fonctionne pour moi.

new Thread(() =>
        {

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

          //Your Code here.

        }, null);
        }).Start();
nPcomp
la source
3

J'ai également constaté que ce System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()n'est pas toujours le répartiteur du contrôle des cibles, comme l'a écrit dotNet dans sa réponse. Je n'avais pas accès au propre répartiteur du contrôle, j'ai donc utilisé Application.Current.Dispatcheret cela a résolu le problème.

Paulus Limma
la source
2

Le problème est que vous appelez à GetGridDatapartir d'un thread d'arrière-plan. Cette méthode accède à plusieurs contrôles WPF qui sont liés au thread principal. Toute tentative pour y accéder à partir d'un fil d'arrière-plan entraînera cette erreur.

Afin de revenir au bon thread, vous devez utiliser SynchronizationContext.Current.Post. Cependant, dans ce cas particulier, il semble que la majorité du travail que vous effectuez soit basée sur l'interface utilisateur. Par conséquent, vous créez un thread d'arrière-plan pour revenir immédiatement au thread d'interface utilisateur et effectuer un travail. Vous devez refactoriser un peu votre code afin qu'il puisse effectuer le travail coûteux sur le thread d'arrière-plan, puis publier les nouvelles données sur le thread d'interface utilisateur par la suite

JaredPar
la source
2

Comme mentionné ici , Dispatcher.Invokepourrait geler l'interface utilisateur. Devrait utiliser à la Dispatcher.BeginInvokeplace.

Voici une classe d'extension pratique pour simplifier la vérification et l'appel du répartiteur d'appel.

Exemple d'utilisation: (appel depuis la fenêtre WPF)

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

Classe d'extension:

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
la source
0

En outre, une autre solution consiste à garantir que vos contrôles sont créés dans le thread d'interface utilisateur, et non par un thread de travail d'arrière-plan par exemple.

FindOutIslamNow
la source
0

J'ai continué à recevoir l'erreur lorsque j'ai ajouté des zones de liste déroulante en cascade à mon application WPF et j'ai résolu l'erreur en utilisant cette 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 );

    }

Pour plus d'informations, consultez https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization) ;k( TargetFrameworkMoniker - .NETFramework,V % 3Dv4.7); k (DevLang-csharp) & rd = true

user8128167
la source