web-dev-qa-db-fra.com

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

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; dans l’obtention des données de la grille lève une exception

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

Quel est le problème ici?

296
Kuntady Nithesh

C'est un problème fréquent 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 est propriétaire du contrôle. S'il en est le propriétaire, votre code a l'air normal. Sinon, utilisez le motif ci-dessus.

618
Candide

Un autre bon usage de Dispatcher.Invoke consiste à mettre immédiatement à jour l'interface utilisateur dans une fonction effectuant d'autres tâches:

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

J'utilise ceci pour mettre à jour le texte du bouton en "Processing ..." et le désactiver lors des requêtes WebClient.

46
computerGuyCJ

Pour ajouter mes 2 centimes, l’exception peut se produire même si vous appelez votre code par le biais de System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
Le fait est que vous devez appeler Invoke() de la Dispatcher du contrôle auquel vous essayez d'accéder , qui dans certains cas peut différer de System.Windows.Threading.Dispatcher.CurrentDispatcher. Vous devez donc utiliser YourControl.Dispatcher.Invoke() pour être sûr. Je me suis cogné la tête pendant quelques heures avant de m'en rendre compte.

Mise à jour

Pour les futurs lecteurs, il semble que cela ait changé dans les nouvelles versions de .NET (4.0 et versions ultérieures). Désormais, vous n'avez plus à vous préoccuper du bon répartiteur lors de la mise à jour des propriétés de sauvegarde de l'interface utilisateur de votre VM. Le moteur WPF organisera des appels cross-thread sur le thread d'interface utilisateur correct. Voir plus de détails ici . Merci à @aaronburro pour l'info et le lien. Vous voudrez peut-être aussi lire notre conversation ci-dessous dans les commentaires.

37
dotNET

Si vous rencontrez ce problème et que des contrôles d'interface utilisateur ont été créés sur un thread travailleur séparé lorsque vous utilisez BitmapSource ou ImageSource dans WPF, appelez d'abord la méthode Freeze() avant de transmettre le message. BitmapSource ou ImageSource en tant que paramètre de toute méthode. L'utilisation de Application.Current.Dispatcher.Invoke() ne fonctionne pas dans de tels cas

30
juFo

c'est arrivé avec moi parce que j'ai essayé de access UI composant dans another 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, envelopper tout appel d'interface utilisateur à l'intérieur ce que Candide a mentionné plus haut 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 );
}
22
Basheer AL-MOMANI

Pour une raison quelconque, la réponse de Candide n'a pas été construite. C'était utile, cependant, car cela m'a amené à trouver ceci qui a parfaitement fonctionné:

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
    {
       //your code here...
    }));
14
Sarah

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

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

J'ai aussi constaté que System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke() n'est pas toujours un répartiteur de contrôle de cible, tout comme dotNet l'a écrit dans sa réponse. Je n'avais pas accès au propre répartiteur du contrôle, j'ai donc utilisé Application.Current.Dispatcher et le problème a été résolu.

3
Paulus Limma

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

Pour revenir au bon fil, utilisez SynchronizationContext.Current.Post. Cependant, dans ce cas particulier, il semble que la majorité du travail que vous effectuez repose sur l'assurance-chômage. Par conséquent, vous créez un thread d'arrière-plan juste pour revenir immédiatement au thread d'interface utilisateur et effectuer un travail. Vous devez modifier un peu votre code pour qu'il puisse effectuer le travail coûteux sur le thread d'arrière-plan, puis poster les nouvelles données sur le thread d'interface utilisateur.

2
JaredPar

Cela fonctionne pour moi.

new Thread(() =>
        {

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

          //Your Code here.

        }, null);
        }).Start();
1
nPcomp

Comme mentionné ici , Dispatcher.Invoke pourrait geler l'interface utilisateur. Devrait utiliser Dispatcher.BeginInvoke à la place.

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();
        }
    }
}
1
Jeson Martajaya

J'ai continué à avoir l'erreur lorsque j'ai ajouté des combobox 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 de détails, veuillez consulter 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

0
user8128167

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

0
FindOutIslamNow