Utas panggilan tidak dapat mengakses objek ini karena utas yang berbeda memilikinya

341

Kode saya seperti di bawah ini

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

Langkah objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;dalam mendapatkan data grid melempar pengecualian

Utas panggilan tidak dapat mengakses objek ini karena utas yang berbeda memilikinya.

Ada apa di sini?

Kuntady Nithesh
sumber

Jawaban:

697

Ini adalah masalah umum dengan orang yang memulai. Setiap kali Anda memperbarui elemen UI Anda dari utas selain utas utama, Anda perlu menggunakan:

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

Anda juga dapat menggunakan control.Dispatcher.CheckAccess()untuk memeriksa apakah utas saat ini memiliki kontrol. Jika memang memilikinya, kode Anda terlihat seperti biasa. Jika tidak, gunakan pola di atas.

Candide
sumber
3
Saya memiliki masalah yang sama dengan OP; Masalah saya sekarang adalah bahwa kejadian tersebut menyebabkan stack overflow sekarang. : \
Malavos
2
Kembali ke proyek lama saya dan menyelesaikan ini. Juga, saya lupa memberi ini +1. Metode ini bekerja dengan sangat baik! Ini meningkatkan waktu pemuatan aplikasi saya dalam 10 detik atau lebih, hanya dengan menggunakan utas untuk memuat sumber daya kami yang terlokalisasi. Bersulang!
Malavos
4
Jika saya tidak salah, Anda bahkan tidak bisa membaca objek UI dari utas non-pemilik; sedikit mengejutkan saya.
Elliot
32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);untuk mendapatkan dispatcher jika tidak pada utas UI sesuai jawaban ini
JumpingJezza
2
+1. Ha! Saya menggunakan ini untuk beberapa peretasan WPF untuk menjaga hal-hal dipisahkan. Saya berada dalam konteks statis jadi saya tidak bisa menggunakan this.Dispatcher.Invoke.... sebaliknya ... myControl.Dispatcher.Invoke:) Saya harus mengembalikan objek kembali jadi saya lakukan myControlDispatcher.Invoke<object>(() => myControl.DataContext);
C. Tewalt
52

Penggunaan lain yang baik Dispatcher.Invokeadalah untuk segera memperbarui UI dalam fungsi yang melakukan tugas-tugas lain:

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

Saya menggunakan ini untuk memperbarui teks tombol ke " Memproses ... " dan menonaktifkannya saat membuat WebClientpermintaan.

computerGuyCJ
sumber
4
Jawaban ini sedang dibahas di Meta. meta.stackoverflow.com/questions/361844/...
JDB masih ingat Monica
Ini menghentikan kendali saya untuk mendapatkan data dari internet?
Waseem Ahmad Naeem
41

Untuk menambahkan 2 sen saya, pengecualian dapat terjadi bahkan jika Anda memanggil kode Anda System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
Intinya adalah bahwa Anda harus menelepon Invoke()dari Dispatchersatu kontrol yang Anda coba akses , yang dalam beberapa kasus mungkin tidak sama System.Windows.Threading.Dispatcher.CurrentDispatcher. Jadi, alih-alih, Anda harus menggunakannya YourControl.Dispatcher.Invoke()agar aman. Saya membenturkan kepala selama beberapa jam sebelum saya menyadari hal ini.

Memperbarui

Untuk pembaca masa depan, sepertinya ini telah berubah di versi .NET (4.0 dan di atas) yang lebih baru. Sekarang Anda tidak perlu lagi khawatir tentang operator yang tepat ketika memperbarui properti dukungan UI di VM Anda. Mesin WPF akan marshal panggilan lintas-thread pada utas UI yang benar. Lihat lebih detail di sini . Terima kasih kepada @aaronburro untuk info dan tautannya. Anda mungkin juga ingin membaca percakapan kami di bawah ini dalam komentar.

dotNET
sumber
4
@ l33t: WPF mendukung banyak utas UI dalam satu aplikasi, yang masing-masing akan memiliki utas sendiri Dispatcher. Dalam kasus-kasus tersebut (yang memang jarang), menelepon Control.Dispatcheradalah pendekatan yang aman. Untuk referensi Anda dapat melihat artikel ini serta pos SO ini (terutama jawaban Squidward).
dotNET
1
Menariknya, saya menghadapi pengecualian ini ketika saya mencari di Google dan menemukan halaman ini dan seperti kebanyakan dari kita, mencoba jawaban dengan suara tertinggi, yang tidak menyelesaikan masalah saya saat itu. Saya kemudian menemukan alasan ini dan mempostingnya di sini untuk rekan pengembang.
dotNET
1
@ l33t, jika Anda menggunakan MVVM dengan benar, maka itu seharusnya tidak menjadi masalah. Tampilan harus tahu Dispatcher apa yang digunakannya, sementara ViewModels dan Model tidak tahu apa-apa tentang kontrol dan tidak perlu tahu tentang kontrol.
aaronburro
1
@ aaronburro: Masalahnya adalah VM mungkin ingin meluncurkan tindakan pada utas alternatif (mis. Tugas, tindakan berbasis-waktu, kueri paralel), dan ketika operasi berlangsung, mungkin ingin memperbarui UI (melalui RaisePropertyChanged dll), yang pada gilirannya akan mencoba untuk mengakses kontrol UI dari utas non-UI dan dengan demikian menghasilkan pengecualian ini. Saya tidak tahu tentang pendekatan MVVM yang benar yang akan memecahkan masalah ini.
dotNET
1
Mesin pengikat WPF secara otomatis mengatur perubahan properti properti ke Dispatcher yang benar. Inilah sebabnya mengapa VM tidak perlu tahu tentang Dispatcher; yang harus dilakukan hanyalah meningkatkan acara yang diubah properti. Mengikat WinForms adalah cerita yang berbeda.
aaronburro
34

Jika Anda mengalami masalah ini dan Kontrol UI dibuat pada utas pekerja terpisah saat bekerja dengan BitmapSourceatau ImageSourcedi WPF, panggil Freeze()metode terlebih dahulu sebelum meneruskan BitmapSourceatau ImageSourcesebagai parameter ke metode apa pun. Penggunaan Application.Current.Dispatcher.Invoke()tidak berfungsi dalam kasus seperti itu

juFo
sumber
24
Ah, tidak ada yang seperti trik tua yang baik dan misterius untuk menyelesaikan sesuatu yang tidak dipahami oleh siapa pun.
Edwin
2
Saya ingin informasi lebih lanjut tentang mengapa ini bekerja dan bagaimana saya bisa mengetahuinya sendiri.
Xavier Shay
25

ini terjadi pada saya karena saya mencoba access UIkomponen dalamanother thread insted of UI thread

seperti ini

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

untuk mengatasi masalah ini, bungkus semua panggilan ui di dalam apa yang disebutkan Candide di atas dalam jawabannya

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
sumber
1
Terpilih, karena ini bukan jawaban duplikat atau plagiaristik, tetapi sebaliknya memberikan contoh yang baik bahwa jawaban lain kurang, sambil memberikan kredit untuk apa yang diposting sebelumnya.
Panzercrisis
Suara positif adalah untuk jawaban yang jelas. Meski sama ditulis oleh orang lain, tetapi ini membuatnya jelas bagi siapa saja yang macet.
NishantM
15

Untuk beberapa alasan jawaban Candide tidak membangun. Namun, itu sangat membantu, karena itu menuntun saya untuk menemukan ini, yang bekerja dengan sempurna:

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
   //your code here...
}));
Sarah
sumber
Mungkin saja Anda tidak menelepon dari kelas formulir. Entah Anda dapat mengambil referensi ke Window, atau Anda mungkin dapat menggunakan apa yang Anda sarankan.
Simone
4
Jika berhasil, Anda tidak perlu menggunakannya di tempat pertama. System.Windows.Threading.Dispatcher.CurrentDispatcheradalah dispatcher untuk utas saat ini . Itu berarti jika Anda berada di utas latar, ia tidak akan menjadi pengirim utas UI. Untuk mengakses dispatcher utas UI, gunakan System.Windows.Application.Current.Dispatcher.
13

Anda perlu Perbarui ke UI, Jadi gunakan

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

Ini bekerja untuk saya.

new Thread(() =>
        {

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

          //Your Code here.

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

Saya juga menemukan bahwa System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()tidak selalu operator target control, seperti yang ditulis dotNet dalam jawabannya. Saya tidak memiliki akses ke operator kontrol sendiri, jadi saya menggunakan Application.Current.Dispatcherdan itu memecahkan masalah.

Paulus Limma
sumber
2

Masalahnya adalah bahwa Anda menelepon GetGridDatadari utas latar belakang. Metode ini mengakses beberapa kontrol WPF yang terikat pada utas utama. Upaya apa pun untuk mengaksesnya dari utas latar belakang akan menyebabkan kesalahan ini.

Untuk kembali ke utas yang benar, Anda harus menggunakan SynchronizationContext.Current.Post. Namun dalam kasus khusus ini sepertinya sebagian besar pekerjaan yang Anda lakukan berbasis UI. Oleh karena itu Anda akan membuat utas latar hanya untuk segera kembali ke utas UI dan melakukan beberapa pekerjaan. Anda perlu sedikit memperbaiki kode Anda sehingga dapat melakukan pekerjaan mahal pada utas latar belakang dan kemudian memposting data baru ke utas UI setelahnya

JaredPar
sumber
2

Seperti disebutkan di sini , Dispatcher.Invokebisa membekukan UI. Harus menggunakanDispatcher.BeginInvoke saja.

Berikut ini adalah kelas ekstensi yang berguna untuk menyederhanakan pemeriksaan dan panggilan permintaan dispatcher.

Contoh penggunaan: (panggilan dari jendela WPF)

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

Kelas ekstensi:

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
sumber
0

Juga, solusi lain adalah memastikan kontrol Anda dibuat di utas UI, bukan oleh utas pekerja latar belakang misalnya.

TemukanOutIslam Sekarang
sumber
0

Saya terus mendapatkan kesalahan ketika saya menambahkan combobox cascading ke aplikasi WPF saya, dan menyelesaikan kesalahan dengan menggunakan API ini:

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

    }

Untuk perinciannya, silakan lihat https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization);k(TargetFrameworkMoniker-.NETFramework, Vers % 3Dv4.7); k (DevLang-csharp) & rd = true

pengguna8128167
sumber