【发布时间】:2021-05-12 01:00:32
【问题描述】:
编辑我删除了我的代码,因为它不完整且令人困惑,但添加了一个最小的可重现示例。为了澄清我的问题,下面的所有文本都被稍微改写了,添加了评论中的信息,但没有添加任何我在提问时不知道的内容,以使 cmets 与未来的读者相关。我也改了标题。
我正在尝试在后台执行一些计算以使 UI 更具响应性。
在尝试之前,我确保我的 Simulator 类工作正常。我的计算是通过从模拟器中调用类方法来启动的。当我尝试执行计算时(无论是使用 await、TaskFactory 还是其他此类工具),我无法访问存储我需要的 3D 模型的MeshGeometry3D 类成员:'调用线程无法访问这个对象因为另一个线程拥有它。'
我可以通过向任务传递参数来克服这个困难,但是数据量很大,所以如果可行的话,我更愿意在现有对象上工作。
我已经阅读了this、this、this、this、this 和 this,但 这些答案都假设一个 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