เธรดการโทรไม่สามารถเข้าถึงวัตถุนี้ได้เนื่องจากเธรดอื่นเป็นเจ้าของ


342

รหัสของฉันเป็นดังนี้

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

ขั้นตอนobjUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;ในการรับข้อมูลกริดเกิดข้อยกเว้น

เธรดการโทรไม่สามารถเข้าถึงวัตถุนี้ได้เนื่องจากเธรดอื่นเป็นเจ้าของ

เกิดอะไรขึ้นที่นี่?


คำตอบ:


699

นี่เป็นปัญหาที่พบบ่อยกับผู้ที่เริ่มต้นใช้งาน เมื่อใดก็ตามที่คุณอัปเดตองค์ประกอบ UI จากเธรดอื่นนอกเหนือจากเธรดหลักคุณต้องใช้:

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

คุณยังสามารถใช้control.Dispatcher.CheckAccess()เพื่อตรวจสอบว่าเธรดปัจจุบันเป็นเจ้าของตัวควบคุมหรือไม่ หากเป็นเจ้าของรหัสของคุณจะดูเป็นปกติ มิฉะนั้นใช้รูปแบบข้างต้น


3
ฉันมีปัญหาเช่นเดียวกับ OP ปัญหาของฉันตอนนี้คือเหตุการณ์ทำให้ตอนนี้ล้นสแต็ค : \
Malavos

2
กลับไปที่โปรเจ็กต์เก่าของฉันและแก้ไขมัน นอกจากนี้ฉันลืม +1 สิ่งนี้ วิธีนี้ใช้ได้ดีทีเดียว! มันปรับปรุงเวลาในการโหลดแอปพลิเคชันของฉันใน 10 วินาทีหรือมากกว่านั้นเพียงแค่ใช้เธรดเพื่อโหลดทรัพยากรที่แปลแล้วของเรา ไชโย!
Malavos

4
ถ้าฉันไม่ผิดคุณไม่สามารถอ่านวัตถุ UI จากเธรดที่ไม่ใช่เจ้าของได้ ทำให้ฉันประหลาดใจเล็กน้อย
Elliot

32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);การรับดิสแพตเชอร์ถ้าไม่ได้อยู่ในเธรด UI ตามคำตอบนี้
JumpingJezza

2
+1 ฮา! ฉันใช้สิ่งนี้สำหรับการแฮ็กเกอร์ WPF บางอย่างเพื่อแยกสิ่งต่าง ๆ ออกจากกัน ฉันอยู่ในบริบทที่คงที่ดังนั้นฉันไม่สามารถใช้this.Dispatcher.Invoke.... แทน ... myControl.Dispatcher.Invoke:) ฉันต้องการคืนวัตถุกลับมาดังนั้นฉันจึงได้myControlDispatcher.Invoke<object>(() => myControl.DataContext);
C. Tewalt

53

การใช้งานที่ดีอีกอย่างหนึ่งDispatcher.Invokeคือการอัปเดต UI ในฟังก์ชันที่ทำงานอื่น ๆ ทันที:

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

ฉันใช้สิ่งนี้เพื่ออัปเดตข้อความของปุ่มเป็น "กำลังดำเนินการ ... " และปิดการใช้งานในขณะที่ทำการWebClientร้องขอ


4
คำตอบนี้กำลังถูกกล่าวถึงใน Meta meta.stackoverflow.com/questions/361844/…
JDB ยังคงจดจำโมนิก้า

สิ่งนี้ทำให้ฉันไม่สามารถควบคุมข้อมูลจากอินเทอร์เน็ตได้หรือไม่
Waseem Ahmad Naeem

41

หากต้องการเพิ่ม 2 System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()เซ็นต์ของฉันยกเว้นอาจเกิดขึ้นได้ถ้าคุณเรียกรหัสผ่านของคุณ
ประเด็นก็คือว่าคุณมีการโทรInvoke()ของDispatcherของการควบคุมที่คุณกำลังพยายามที่จะเข้าถึงSystem.Windows.Threading.Dispatcher.CurrentDispatcherซึ่งในบางกรณีอาจจะไม่เหมือนกัน ดังนั้นคุณควรใช้YourControl.Dispatcher.Invoke()เพื่อความปลอดภัยแทน ฉันทุบหัวของฉันสองสามชั่วโมงก่อนที่ฉันจะรู้เรื่องนี้

ปรับปรุง

สำหรับผู้อ่านในอนาคตดูเหมือนว่าจะมีการเปลี่ยนแปลงใน. NET เวอร์ชั่นใหม่ (4.0 ขึ้นไป) ตอนนี้คุณไม่ต้องกังวลกับดิสแพตเชอร์ที่ถูกต้องอีกต่อไปเมื่ออัพเดตคุณสมบัติการสำรองข้อมูล UI ใน VM ของคุณ โปรแกรม WPF จะเรียกการข้ามเธรดในเธรด UI ที่ถูกต้อง ดูรายละเอียดเพิ่มเติมที่นี่ ขอบคุณ @aaronburro สำหรับข้อมูลและลิงค์ คุณอาจต้องการอ่านการสนทนาของเราด้านล่างในความคิดเห็น


4
@ l33t: WPF สนับสนุนหัวข้อ UI Dispatcherหลายในโปรแกรมประยุกต์หนึ่งซึ่งแต่ละคนจะมีของตัวเอง ในกรณีเหล่านั้น (ซึ่งเป็นที่ยอมรับได้ยาก) การโทรControl.Dispatcherเป็นวิธีที่ปลอดภัย สำหรับการอ้างอิงคุณสามารถดูบทความนี้เช่นเดียวกับการโพสต์ SO นี้ (โดยเฉพาะอย่างยิ่งคำตอบของ Squidward)
dotNET

1
ที่น่าสนใจฉันกำลังเผชิญกับข้อยกเว้นนี้เมื่อฉัน googled และลงจอดบนหน้านี้และเหมือนที่พวกเราส่วนใหญ่ทำลองคำตอบที่ได้รับการโหวตสูงสุดซึ่งไม่ได้แก้ปัญหาของฉันในตอนนั้น ฉันพบเหตุผลนี้แล้วโพสต์ไว้ที่นี่เพื่อผู้พัฒนาเพียร์
dotNET

1
@ l33t หากคุณใช้ MVVM ถูกต้องแสดงว่าไม่น่าจะมีปัญหา มุมมองจำเป็นต้องรู้ว่า Dispatcher ที่ใช้อยู่ในขณะที่ ViewModels และรุ่นต่าง ๆ ไม่รู้จักการควบคุมและไม่จำเป็นต้องรู้จักการควบคุม
aaronburro

1
@aaronburro: ปัญหาคือ VM อาจต้องการเปิดใช้งานการกระทำบนเธรดทางเลือก (เช่นงาน, การดำเนินการตามตัวจับเวลา, การค้นหาแบบขนาน) และเมื่อการดำเนินการดำเนินไปเรื่อย ๆ อาจต้องการอัปเดต UI (ผ่าน RaisePropertyChanged เป็นต้น) เพื่อเข้าถึงการควบคุม UI จากเธรดที่ไม่ใช่ UI และทำให้เกิดข้อยกเว้นนี้ ฉันไม่รู้วิธี MVVM ที่ถูกต้องที่จะแก้ปัญหานี้
dotNET

1
เอ็นจินการรวม WPF marshals คุณสมบัติการเปลี่ยนแปลงเหตุการณ์โดยอัตโนมัติไปยังโปรแกรมเลือกจ่ายงานที่ถูกต้อง นี่คือเหตุผลที่ VM ไม่จำเป็นต้องรู้จักโปรแกรมเลือกจ่ายงาน สิ่งที่ต้องทำก็แค่เพิ่มกิจกรรมที่เปลี่ยนแปลง WinForms ผูกพันเป็นเรื่องราวที่แตกต่าง
aaronburro

34

หากคุณพบปัญหานี้และการควบคุม UI ถูกสร้างขึ้นในเธรดผู้ปฏิบัติงานแยกต่างหากเมื่อทำงานกับBitmapSourceหรือImageSourceใน WPF ให้เรียกFreeze()วิธีการก่อนที่จะส่งผ่านBitmapSourceหรือImageSourceเป็นพารามิเตอร์ไปยังวิธีการใด ๆ การใช้งานApplication.Current.Dispatcher.Invoke()ไม่ได้ในกรณีเช่นนี้


24
อาไม่มีอะไรที่เหมือนความคลุมเครือเก่า ๆ และกลอุบายลึกลับที่จะแก้ปัญหาที่ไม่มีใครเข้าใจ
Edwin

2
ฉันรักข้อมูลเพิ่มเติมเกี่ยวกับสาเหตุการทำงานและวิธีการที่ฉันสามารถหาได้ด้วยตนเอง
Xavier Shay


25

สิ่งนี้เกิดขึ้นกับฉันเพราะฉันพยายามที่จะจัดaccess UIองค์ประกอบanother thread insted of UI thread

แบบนี้

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

เพื่อแก้ปัญหานี้ให้ใช้การเรียก UI ใด ๆ ภายในสิ่งที่ Candide ระบุไว้ข้างต้นในคำตอบของเขา

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

1
upvoted เพราะนี้เป็นไม่ได้คำตอบที่ซ้ำกันหรือ plagiaristic แต่แทนที่จะให้เป็นตัวอย่างที่ดีที่คำตอบอื่น ๆ กำลังขาดแคลนในขณะที่ให้เครดิตสำหรับสิ่งที่ถูกโพสต์ก่อนหน้านี้
Panzercrisis

การโหวตคือคำตอบที่ชัดเจน แม้ว่าจะเหมือนกันถูกเขียนโดยคนอื่น ๆ แต่สิ่งนี้ทำให้ชัดเจนสำหรับทุกคนที่ติดอยู่
NishantM

15

ด้วยเหตุผลบางอย่างคำตอบของ Candide ไม่ได้สร้างขึ้น มันเป็นประโยชน์ แต่เนื่องจากมันทำให้ฉันพบสิ่งนี้ซึ่งทำงานได้อย่างสมบูรณ์:

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

เป็นไปได้ว่าคุณไม่ได้โทรจากชั้นเรียนของแบบฟอร์ม คุณสามารถคว้าการอ้างอิงไปที่หน้าต่างหรือคุณอาจใช้สิ่งที่คุณแนะนำ
Simone

4
ถ้ามันเหมาะกับคุณมันไม่จำเป็นที่จะใช้มันตั้งแต่แรก System.Windows.Threading.Dispatcher.CurrentDispatcherเป็นผู้มอบหมายงานสำหรับเธรดปัจจุบัน ซึ่งหมายความว่าหากคุณอยู่ในเธรดพื้นหลังจะไม่เป็นตัวเลือกโปรแกรมส่งข้อความของ UI ในการเข้าถึงหัวข้อ UI System.Windows.Application.Current.Dispatcherของรีบใช้


4

มันใช้งานได้สำหรับฉัน

new Thread(() =>
        {

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

          //Your Code here.

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

3

ฉันยังพบว่าไม่ได้System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()เป็นผู้ควบคุมเป้าหมายเสมอเหมือนดอทเน็ตเขียนไว้ในคำตอบของเขา ฉันไม่สามารถเข้าถึงโปรแกรมเลือกจ่ายงานของตัวควบคุมได้ดังนั้นฉันจึงใช้Application.Current.Dispatcherและมันแก้ปัญหาได้


2

ปัญหาคือคุณโทรGetGridDataจากเธรดพื้นหลัง วิธีนี้เข้าถึงการควบคุม WPF หลายอย่างซึ่งผูกไว้กับเธรดหลัก ความพยายามในการเข้าถึงจากเธรดพื้นหลังจะนำไปสู่ข้อผิดพลาดนี้

SynchronizationContext.Current.Postเพื่อที่จะได้รับกลับไปด้ายที่ถูกต้องที่คุณควรใช้ อย่างไรก็ตามในกรณีนี้ดูเหมือนว่างานส่วนใหญ่ที่คุณทำนั้นเป็นแบบอิง UI ดังนั้นคุณจะต้องสร้างเธรดพื้นหลังเพื่อกลับไปยังเธรด UI และทำงานได้ทันที คุณต้อง refactor รหัสของคุณเล็กน้อยเพื่อให้สามารถทำงานได้อย่างมีราคาแพงบนเธรดพื้นหลังแล้วโพสต์ข้อมูลใหม่ไปยังเธรด UI หลังจากนั้น


2

เป็นที่กล่าวถึงที่นี่ , Dispatcher.Invokeจะแช่แข็งของ UI ควรใช้Dispatcher.BeginInvokeแทน

นี่คือคลาสส่วนขยายที่ใช้งานง่ายเพื่อลดความซับซ้อนในการตรวจสอบและเรียกการมอบหมายงานโปรแกรมเลือกจ่าย

การใช้งานตัวอย่าง: (โทรจากหน้าต่าง WPF)

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

คลาสส่วนขยาย:

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

0

อีกวิธีหนึ่งคือให้แน่ใจว่าการควบคุมของคุณถูกสร้างขึ้นในเธรด UI ไม่ใช่โดยเธรดผู้ทำงานเบื้องหลัง


0

ฉันยังคงได้รับข้อผิดพลาดเมื่อเพิ่ม cascading comboboxes ในแอปพลิเคชัน WPF ของฉันและแก้ไขข้อผิดพลาดโดยใช้ 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 );

    }

สำหรับรายละเอียดโปรดดู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) และถ = true

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.