【问题标题】:The calling thread cannot access this object - NOT AN UI OBJECT [duplicate]调用线程无法访问此对象 - 不是 UI 对象 [重复]
【发布时间】:2021-05-12 01:00:32
【问题描述】:

编辑我删除了我的代码,因为它不完整且令人困惑,但添加了一个最小的可重现示例。为了澄清我的问题,下面的所有文本都被稍微改写了,添加了评论中的信息,但没有添加任何我在提问时不知道的内容,以使 cmets 与未来的读者相关。我也改了标题。

我正在尝试在后台执行一些计算以使 UI 更具响应性。

在尝试之前,我确保我的 Simulator 类工作正常。我的计算是通过从模拟器中调用类方法来启动的。当我尝试执行计算时(无论是使用 await、TaskFactory 还是其他此类工具),我无法访问存储我需要的 3D 模型的MeshGeometry3D 类成员:'调用线程无法访问这个对象因为另一个线程拥有它。'

我可以通过向任务传递参数来克服这个困难,但是数据量很大,所以如果可行的话,我更愿意在现有对象上工作。

我已经阅读了thisthisthisthisthisthis,但 这些答案都假设一个 UI 元素,但事实并非如此:在后台计算之前、期间或之后,我的 MeshGeometry3D 成员未在 UI 中使用。

我读到这是人们入门时的常见问题,我应该在尝试访问属于主线程的数据时调用调度程序,但我无法从我的模拟器类访问调度程序(附带问题,有没有办法访问创建元素的调度程序?)

所以我试图了解正在发生的事情,以便确定处理问题的最佳方法。从我目前阅读的内容来看,我看到了 2 个选项:

  • 使用调度程序访问对象
  • 每次执行计算时都会创建罪魁祸首 MeshGeometry3D

最小的可重现示例

MainWindow.xaml.cs

using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Media3D;

namespace DispatcherObject
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private Simulator Simulator;

        public MainWindow()
        {
            InitializeComponent();
            Simulator = new Simulator();
        }                  

        private void CreateMesh_Click(object sender, RoutedEventArgs e)
        {
            Simulator.mesh = new MeshGeometry3D();
        }

        private void AccessMesh_Click(object sender, RoutedEventArgs e)
        {
            CallSimulatorMethod();
        }  

        private async void CallSimulatorMethod()
        {
            await Task.Run(() =>Simulator.AccessMesh());
        }
    }
}

模拟器.cs

using System.Windows.Media.Media3D;

namespace DispatcherObject
{
    class Simulator
    {
        public MeshGeometry3D mesh;

        public void AccessMesh()
        {
            var foo = mesh.Normals;
        }
    }
}

使用前面的代码,如果你先点击 AccessMesh 按钮,你当然会得到一个 System.NullReferenceException。

如果你先点击 CreateMesh,你会得到这个异常:

System.InvalidOperationException HResult=0x80131509 Message=调用线程无法访问此对象,因为不同的线程拥有它。源 = WindowsBase StackTrace:在 System.Windows.Threading.Dispatcher.VerifyAccess() 在 System.Windows.DependencyObject.GetValue(DependencyProperty dp) 在 System.Windows.Media.Media3D.MeshGeometry3D.get_Normals() 在 DispatcherObject.Simulator.AccessMesh() 在 C:\Users\geraud\source\repos\DispatcherObject\DispatcherObject\Simulator.cs:line 11 在 DispatcherObject.MainWindow.b__4_0() 中 C:\Users\geraud\source\repos\DispatcherObject\DispatcherObject\MainWindow.xaml.cs:line 33 在 System.Threading.Tasks.Task.InnerInvoke() 在 System.Threading.Tasks.Task.Execute()

此异常最初是在此调用堆栈中引发的: [外部代码] Simulator.cs 中的 DispatcherObject.Simulator.AccessMesh() DispatcherObject.MainWindow.CallSimulatorMethod.AnonymousMethod__4_0() 在 MainWindow.xaml.cs [外部代码]

【问题讨论】:

  • 任务不是线程。线程不拥有类或字段。 The calling thread cannot access this object because a different thread owns it 根本没有类和字段。在所有操作系统中,只有 UI 线程可以修改 UI 元素。没有办法解决这个问题。这在 C# 和 TPL 中通过使用async/await解决。在后台执行任何并行/并发/异步操作,使用await等待这些操作完成而不阻塞UI,用新数据更新UI
  • 您是否从更新任何 UI组件作为过程的一部分(或过程的结果)。 IE。您是在更新表单还是控件中的值?显而易见的答案是“是”,因为这就是您获得此消息的方式。我只是以不同的方式解释上述 cmets,可能会对您有所帮助
  • @geriwald 发布您的代码!除了两个事实之外,我们也无法理解发生了什么——一个后台线程正在修改 UI(例外证明了这一点)和任务使用不当(new Task() 证明了这一点)。我们不知道代码是什么,或者任务是做什么的
  • MeshGeometry3D 继承自 DispatcherObject,明确指出:“只有创建 Dispatcher 的线程才能直接访问 DispatcherObject”。通常这将是一个 UI 线程,但不一定是。

标签: c# multithreading dispatcher multitasking


【解决方案1】:

感谢所有帮助我找到答案的评论员,尤其是@Knoop。

我试图访问一个继承自 DispatcherObjectMeshGeometry3D,来自创建它的线程所在的不同线程。我通过在工作线程中重新创建对象摆脱了异常:

只有创建 Dispatcher 的线程才能访问 直接 DispatcherObject。

我想强调 这不是 UI 问题:虽然异常没有指定任何有关 UI 的内容,但它似乎主要是在 UI 对象中遇到的,因为 cmets 在我和 Stack Overflow 中的所有其他相关问题都清楚地显示了。

【讨论】:

  • 这很可能是处理异常的最糟糕的方式,因为当您创建DispatcherObject 时,它会在线程上隐式创建一个新的Dispatcher。 Dispatcher 对象采用这种方式是有原因的,并且作为一般规则,应该只在原始的 Dispatcher 线程中使用,即主 UI 线程。可能存在有效的例外,但正确识别此类例外的人首先会知道原始 InvalidOperationException 的含义。
猜你喜欢
  • 2013-07-31
  • 2012-03-22
  • 1970-01-01
  • 1970-01-01
  • 2014-02-13
  • 2012-06-03
  • 1970-01-01
  • 2011-02-13
相关资源
最近更新 更多