Chuỗi cuộc gọi không thể truy cập đối tượng này vì một luồng khác sở hữu nó


341

Mã của tôi là như dưới đây

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

Bước objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;lấy dữ liệu lưới ném ngoại lệ

Chuỗi cuộc gọi không thể truy cập đối tượng này vì một luồng khác sở hữu nó.

Có chuyện gì ở đây vậy?


Câu trả lời:


697

Đây là một vấn đề phổ biến với những người bắt đầu. Bất cứ khi nào bạn cập nhật các thành phần UI của mình từ một luồng khác ngoài luồng chính, bạn cần sử dụng:

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

Bạn cũng có thể sử dụng control.Dispatcher.CheckAccess()để kiểm tra xem luồng hiện tại có sở hữu điều khiển hay không. Nếu nó sở hữu nó, mã của bạn trông như bình thường. Nếu không, sử dụng mô hình trên.


3
Tôi có cùng một vấn đề như OP; Vấn đề của tôi bây giờ là sự kiện này gây ra tình trạng tràn ngăn xếp. : \
Malavos

2
Đã trở lại dự án cũ của tôi và giải quyết điều này. Ngoài ra, tôi đã quên +1 này. Phương pháp này hoạt động khá tốt! Nó cải thiện thời gian tải ứng dụng của tôi trong 10 giây hoặc thậm chí hơn, chỉ bằng cách sử dụng các luồng để tải tài nguyên cục bộ của chúng tôi. Chúc mừng!
Malavos

4
Nếu tôi không sai, bạn thậm chí không thể đọc một đối tượng UI từ một chủ đề không phải là chủ sở hữu; làm tôi ngạc nhiên một chút.
Elliot

32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);để có được bộ điều phối nếu không có trong giao diện người dùng theo câu trả lời này
JumpingJezza

2
+1. Hà! Tôi đã sử dụng điều này cho một số tin tặc WPF để giữ cho mọi thứ tách rời. Tôi đã ở trong một bối cảnh tĩnh vì vậy tôi không thể sử dụng this.Dispatcher.Invoke.... thay vào đó ... myControl.Dispatcher.Invoke:) Tôi cần phải trả lại một đối tượng nên tôi đã làm vậy myControlDispatcher.Invoke<object>(() => myControl.DataContext);
C. Tewalt 14/2/2016

52

Một cách sử dụng tốt khác Dispatcher.Invokelà cập nhật giao diện người dùng ngay lập tức trong một chức năng thực hiện các tác vụ khác:

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

Tôi sử dụng điều này để cập nhật văn bản nút thành "Đang xử lý ... " và vô hiệu hóa nó trong khi thực hiện WebClientcác yêu cầu.


4
Câu trả lời này đang được thảo luận trên Meta. meta.stackoverflow.com/questions/361844/ Mạnh
JDB vẫn còn nhớ Monica

Điều này đã ngăn sự kiểm soát của tôi lấy dữ liệu từ internet?
Waseem Ahmad Naeem

41

Để thêm 2 xu của tôi, ngoại lệ có thể xảy ra ngay cả khi bạn gọi mã của mình thông qua System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
Vấn đề là bạn phải gọi Invoke()của Dispatchercác điều khiển mà bạn đang cố gắng truy cập , mà trong một số trường hợp có thể không giống như System.Windows.Threading.Dispatcher.CurrentDispatcher. Vì vậy, thay vào đó bạn nên sử dụng YourControl.Dispatcher.Invoke()để được an toàn. Tôi đã đập đầu mình trong vài giờ trước khi tôi nhận ra điều này.

Cập nhật

Đối với độc giả trong tương lai, có vẻ như điều này đã thay đổi trong các phiên bản .NET mới hơn (4.0 trở lên). Bây giờ bạn không còn phải lo lắng về bộ điều phối chính xác khi cập nhật các thuộc tính sao lưu UI trong VM của bạn. Công cụ WPF sẽ sắp xếp các cuộc gọi luồng chéo trên luồng UI chính xác. Xem thêm chi tiết tại đây . Cảm ơn @aaronburro cho thông tin và liên kết. Bạn cũng có thể muốn đọc cuộc trò chuyện của chúng tôi dưới đây trong các ý kiến.


4
@ l33t: WPF hỗ trợ nhiều luồng UI trong một ứng dụng, mỗi luồng sẽ có một luồng riêng Dispatcher. Trong những trường hợp (được thừa nhận là hiếm), gọi điện Control.Dispatcherlà phương pháp an toàn. Để tham khảo, bạn có thể xem bài viết này cũng như bài viết SO này (đặc biệt là câu trả lời của Squidward).
dotNET

1
Thật thú vị, tôi đã phải đối mặt với ngoại lệ này khi tôi googled và hạ cánh trên trang này và giống như hầu hết chúng ta, đã thử câu trả lời được bình chọn cao nhất, điều đó không giải quyết được vấn đề của tôi sau đó. Sau đó tôi đã tìm ra lý do này và đăng nó ở đây cho các nhà phát triển ngang hàng.
dotNET

1
@ l33t, nếu bạn đang sử dụng MVVM chính xác, thì đó không phải là vấn đề. Khung nhìn nhất thiết phải biết những gì Dispatcher đang sử dụng, trong khi ViewModels và Mô hình không biết gì về điều khiển và không cần biết điều khiển.
aaronburro

1
@aaronburro: Vấn đề là VM có thể muốn khởi chạy các hành động trên các luồng thay thế (ví dụ: Nhiệm vụ, hành động dựa trên bộ đếm thời gian, truy vấn song song) và khi tiến hành hoạt động, có thể muốn cập nhật giao diện người dùng (thông qua RaisePropertyChanged, v.v.), sẽ lần lượt thử để truy cập điều khiển UI từ luồng không phải UI và do đó dẫn đến ngoại lệ này. Tôi không biết một cách tiếp cận MVVM chính xác sẽ giải quyết vấn đề này.
dotNET

1
Công cụ ràng buộc WPF tự động sắp xếp các sự kiện thay đổi thuộc tính thành Bộ điều chỉnh chính xác. Đây là lý do tại sao VM không cần biết về Bộ điều phối; tất cả những gì nó phải làm chỉ là nâng cao sự kiện thay đổi tài sản. WinForms ràng buộc là một câu chuyện khác nhau.
aaronburro

34

Nếu bạn gặp phải vấn đề này và Điều khiển giao diện người dùng đã được tạo trên một luồng công nhân riêng biệt khi làm việc với BitmapSourcehoặc ImageSourcetrong WPF, Freeze()trước tiên hãy gọi phương thức trước khi chuyển BitmapSourcehoặc ImageSourcelàm tham số cho bất kỳ phương thức nào. Sử dụng Application.Current.Dispatcher.Invoke()không hoạt động trong những trường hợp như vậy


24
Ah, không có gì giống như một mánh khóe mơ hồ và bí ẩn cũ để giải quyết một cái gì đó không ai hiểu.
Edwin

2
Tôi muốn biết thêm thông tin về lý do tại sao điều này hoạt động và làm thế nào tôi có thể tự mình tìm ra nó.
Xavier Shay


25

điều này xảy ra với tôi bởi vì tôi đã cố gắng để access UIthành phần tronganother thread insted of UI thread

như thế này

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

để giải quyết vấn đề này, hãy bọc bất kỳ cuộc gọi ui nào bên trong những gì Candide đã đề cập ở trên trong câu trả lời của anh ấy

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
Upvote, bởi vì đây không phải là một câu trả lời trùng lặp hoặc đạo văn, nhưng nó thay vào đó cung cấp một ví dụ tốt rằng các câu trả lời khác đang thiếu, trong khi cung cấp tín dụng cho những gì đã được đăng trước đó.
Panzercrisis

Upvote là cho một câu trả lời rõ ràng. Mặc dù cùng được viết bởi những người khác, nhưng điều này làm cho nó rõ ràng cho bất cứ ai bị mắc kẹt.
NishantM

15

Vì một số lý do, câu trả lời của Candide đã không được xây dựng. Mặc dù vậy, nó rất hữu ích vì nó đã khiến tôi tìm thấy cái này, nó hoạt động hoàn hảo:

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

Có thể là bạn đã không gọi từ lớp của mẫu. Hoặc bạn có thể lấy một tham chiếu đến Cửa sổ, hoặc bạn có thể sử dụng những gì bạn đề xuất.
Simone

4
Nếu nó làm việc cho bạn, không cần thiết phải sử dụng nó ở nơi đầu tiên. System.Windows.Threading.Dispatcher.CurrentDispatcherngười điều phối cho chủ đề hiện tại . Điều đó có nghĩa là nếu bạn đang ở trên một chủ đề nền, nó sẽ không phải là bộ điều phối của giao diện người dùng. Để truy cập bộ điều phối của giao diện người dùng, sử dụng System.Windows.Application.Current.Dispatcher.

13

Bạn cần cập nhật lên UI, nên sử dụng

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

4

Điều này làm việc cho tôi.

new Thread(() =>
        {

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

          //Your Code here.

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

3

Tôi cũng thấy rằng System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()không phải lúc nào cũng là người điều khiển mục tiêu, giống như dotNet đã viết trong câu trả lời của anh ấy. Tôi không có quyền truy cập vào bộ điều phối riêng, vì vậy tôi đã sử dụng Application.Current.Dispatchervà nó đã giải quyết được vấn đề.


2

Vấn đề là bạn đang gọi GetGridDatatừ một chủ đề nền. Phương thức này truy cập vào một số điều khiển WPF được liên kết với luồng chính. Bất kỳ nỗ lực truy cập chúng từ một chủ đề nền sẽ dẫn đến lỗi này.

Để lấy lại đúng chủ đề, bạn nên sử dụng SynchronizationContext.Current.Post. Tuy nhiên trong trường hợp cụ thể này, có vẻ như phần lớn công việc bạn đang làm là dựa trên giao diện người dùng. Do đó, bạn sẽ tạo một luồng nền chỉ để quay lại ngay lập tức giao diện người dùng và thực hiện một số công việc. Bạn cần cấu trúc lại mã của mình một chút để nó có thể thực hiện công việc đắt tiền trên luồng nền và sau đó đăng dữ liệu mới lên luồng UI sau đó


2

Như đã đề cập ở đây , Dispatcher.Invokecó thể đóng băng UI. Nên dùng Dispatcher.BeginInvokethay thế.

Dưới đây là một lớp mở rộng tiện dụng để đơn giản hóa việc kiểm tra và gọi lệnh gọi bộ điều phối.

Sử dụng mẫu: (gọi từ cửa sổ WPF)

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

Lớp mở rộng:

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

Ngoài ra, một giải pháp khác là đảm bảo các điều khiển của bạn được tạo trong luồng UI chứ không phải bởi luồng xử lý nền.


0

Tôi liên tục gặp lỗi khi tôi thêm các hộp kết hợp xếp tầng vào ứng dụng WPF của mình và đã khắc phục lỗi bằng cách sử dụng API này:

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

    }

Để biết chi tiết, vui lòng xem https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSyn syncization); k(Target % 3Dv4.7); k (DevLang-csharp) & rd = true

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.