调用线程无法访问此对象,因为其他线程拥有它

本文关键字:线程 因为 其他 拥有 访问 调用 对象 | 更新日期: 2023-09-27 18:27:55

我的代码如下

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;的步骤引发异常

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

这是怎么回事?

调用线程无法访问此对象,因为其他线程拥有它

这是人们入门的常见问题。每当从主线程以外的线程更新 UI 元素时,都需要使用:

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

还可以使用 control.Dispatcher.CheckAccess() 检查当前线程是否拥有该控件。如果它确实拥有它,您的代码看起来很正常。否则,请使用上述模式。

加上我的 2 美分,即使您通过 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

由于这是一个现在很受欢迎的帖子,我想我会分享我在接下来的几年里的经验。行为似乎是任何属性绑定将在跨线程调用中正确更新(无需封送;WPF 将为您处理它(。OTOH 命令绑定需要委托给 UI 调度程序。我已经用MVVM Light和相对较新的社区工具包对其进行了测试,旧框架和新的.NET 5和6似乎都是这种情况。 从非 UI 线程调用时,AsyncRelayCommand无法更新 UI(当从更新按钮的 Enabled 属性的工作线程触发CanExecuteChanged时,会发生这种情况(。当然,解决方案是在启动时将 UI 调度程序存储在 VM 的全局空间中的某个位置,然后在更新 UI 时使用它。

如果在

WPF 中使用 BitmapSourceImageSource 时遇到此问题,并且 UI 控件是在单独的工作线程上创建的,请先调用该方法Freeze()然后再将BitmapSourceImageSource作为参数传递给任何方法。在这种情况下使用 Application.Current.Dispatcher.Invoke()不起作用

发生在我身上,因为我试图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 );
}

您需要在 UI 线程上执行此操作。用:

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

由于某种原因,坎迪德的答案没有建立起来。不过,这很有帮助,因为它让我找到了这个,它完美地工作:

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

这对我有用。

new Thread(() =>
        {
        Thread.CurrentThread.IsBackground = false;
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate {
          //Your Code here.
        }, null);
        }).Start();

如上所述,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();
        }
    }
}

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

问题是您正在从后台线程调用GetGridData。 此方法访问绑定到主线程的多个 WPF 控件。 任何从后台线程访问它们的尝试都将导致此错误。

为了返回到正确的线程,您应该使用 SynchronizationContext.Current.Post . 但是,在这种特殊情况下,您所做的大部分工作似乎都是基于UI的。 因此,您将创建一个后台线程,只是为了立即返回到 UI 线程并执行一些工作。 您需要稍微重构代码,以便它可以在后台线程上完成昂贵的工作,然后将新数据发布到 UI 线程

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

我使用 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 的系统,然后返回以处理已触发的任何事件,而不是高效的跨线程操作。

有时可能是

您创建的对象引发异常,而不是我显然正在查看的目标。

在我的代码中:

XAML 文件:

<Grid Margin="0,0,0,0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
    <TextBlock x:Name="tbScreenLog" VerticalAlignment="Stretch" Background="Black" FontSize="12" Foreground="#FF919191" HorizontalAlignment="Stretch"/>
</Grid>

XAML.cs文件:

System.Windows.Documents.Run rnLine = new System.Windows.Documents.Run(Message.Item2 + "'r'n");
rnLine.Foreground = LineAlternate ? Brushes.Green : Brushes.Orange;
Dispatcher.Invoke(()=> {
    tbScreenLog.Inlines.Add(rnLine);
});
LineAlternate = !LineAlternate;

我收到有关从其他线程访问对象的异常,但我在 UI 线程上调用它??

过了一会儿,我感到畏惧的是,这不是关于 TextBlock 对象,而是关于我在调用之前创建的 Run 对象。

将代码更改为此解决了我的问题:

Dispatcher.Invoke(()=> {
    Run rnLine = new Run(Message.Item2 + "'r'n");
    rnLine.Foreground = LineAlternate ? Brushes.Green : Brushes.Orange;
    tbScreenLog.Inlines.Add(rnLine);
});
LineAlternate = !LineAlternate;
<</div> div class="answers">

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

当我将级联组合框添加到我的 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(&rd=true

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

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

解决方案是将子列表作为 ReadOnlyObservableCollections 移动到 ViewModel,并按当前选定的主元素过滤子元素表的完整列表。

相关文章: