我的代码如下

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

该步骤遵循judmcountrystandards。Country = txtSearchCountry.Text.Trim() !=字符串。空的吗?txtSearchCountry。文本:null;在获取网格数据时抛出异常

调用线程不能访问此对象,因为有不同的 线程拥有它。

这里出了什么问题?


当前回答

补充一点,即使你通过System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()调用你的代码,也会发生异常。关键是你必须调用你试图访问的控件的Dispatcher的Invoke(),在某些情况下可能与System.Windows.Threading.Dispatcher.CurrentDispatcher不相同。因此,为了安全起见,您应该使用YourControl.Dispatcher.Invoke()。我敲了几个小时的头才意识到这一点。

更新

对于未来的读者来说,这一点在。net的新版本(4.0及以上版本)中似乎有所改变。现在,在VM中更新ui支持属性时,您不再需要担心正确的调度程序。WPF引擎将在正确的UI线程上编组跨线程调用。点击这里查看更多细节。感谢@aaronburro提供的信息和链接。你也可以在评论中阅读我们的对话。

更新2

Since this is a popular post now, I thought I'd share my experience that I had in the following years. The behavior seems to be that any property bindings will update correctly in cross-thread calls (no marshalling required; WPF will handle it for you). OTOH command bindings will need to be delegated to the UI dispatcher. I have tested it with both MVVM Light and the relatively new Community Toolkit and it seems to be the case with both the old Framework and the new .NET 5 and 6. AsyncRelayCommand fails to update the UI when invoked from non-UI thread (This happens when CanExecuteChanged is fired from a worker thread which updates, for example, button's Enabled property). The solution of course is to store UI dispatcher somewhere in the global space in your VM upon startup and then use it when updating the UI.

其他回答

另外,另一个解决方案是确保控件是在UI线程中创建的,而不是由后台工作线程创建的。

我在从WPF控件中选择的第二个项目上奇怪地遇到了这个错误。

原因是我将数据加载到RX SourceCache中,加载的元素将observablecollection作为导航属性包装到CollectionView中。observablecollection连接到UIThread,数据由WorkerThread加载。由于CollectionView只在显示第一个元素时被填充,不同线程的问题只发生在被选择的第二个项目上。

解决方案是将子列表作为ReadOnlyObservableCollections移动到ViewModel中,并根据当前选择的主元素筛选子元素表的完整列表。

我还发现System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()并不总是目标控制的调度程序,就像dotNet在他的回答中所写的那样。我没有访问控件自己的调度程序,所以我使用Application.Current.Dispatcher,它解决了这个问题。

正如这里提到的,Dispatcher。调用可以冻结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();
        }
    }
}

根据您的需要,肯定有不同的方法来做到这一点。

我使用UI更新线程(它不是主UI线程)的一种方法是让线程开始一个循环,其中整个逻辑处理循环被调用到UI线程上。

例子:

public SomeFunction()
{
    bool working = true;
    Thread t = new Thread(() =>
    {
        // Don't put the working bool in here, otherwise it will 
        // belong to the new thread and not the main UI thread.
        while (working)
        {
            Application.Current.Dispatcher.Invoke(() =>
            {
                // Put your entire logic code in here.
                // All of this code will process on the main UI thread because
                //  of the Invoke.
                // By doing it this way, you don't have to worry about Invoking individual
                //  elements as they are needed.
            });
        }
    });
}

这样,代码就完全在主UI线程上执行。对于那些难以理解跨线程操作的业余程序员来说,这可能是一种专业方法。然而,对于更复杂的ui(特别是在执行动画时),它很容易成为一个骗局。实际上,这只是为了伪造一个更新UI的系统,然后返回来处理任何已经触发的事件,以代替有效的跨线程操作。