-2

EDIT I removed my code as it was incomplete and confusing, but added a minimal reproducible exemple. All the text below has been slightly rewritten to clarify my question, adding information from the comment, but without adding anything I didn't know when asking the question, in order to keep the comments relevant for a future reader. I also changed the title.

I'm trying to perform some calculations in the background to make UI more responsive.

Before trying that, I made sure my Simulator class works fine. My calculations are launched by calling a class method from Simulator. When I try to perform calculations (whether with await, TaskFactory or other such tools), I can't access the MeshGeometry3D class member that stores the 3D model I need : 'The calling thread cannot access this object because a different thread owns it.'

I could overcome the difficulty by passing arguments to the task, but the volume of data is quite big, so if that's feasable I would prefer working on a existing object.

I have read this, this, this, this, this and this but those answers all assume an UI element, and that's not the case : my MeshGeometry3D member is not used in the UI, before, during or after the background calculation.

I read that this is a common problem with people getting started, and that I should invoke the dispatcher when trying to access data that belong to the main thread, but I don't have access to the dispatcher from my simulator class (side question, is there a way to access the dispatcher that created an element ?)

So I'm trying to understand what is happening, in order to decide the best way to handle the problem. From what I read so far, I see 2 options :

  • Use a dispatcher to access the object
  • Create the culprit MeshGeometry3D each time I perform the calculations

Minimal reproducible example

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

Simulator.cs

using System.Windows.Media.Media3D;

namespace DispatcherObject
{
    class Simulator
    {
        public MeshGeometry3D mesh;

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

With the previous code, if you click the AccessMesh button first, you get a System.NullReferenceException, of course.

If you click CreateMesh first, you get this exception :

System.InvalidOperationException HResult=0x80131509 Message=The calling thread cannot access this object because a different thread owns it. Source=WindowsBase StackTrace: at System.Windows.Threading.Dispatcher.VerifyAccess() at System.Windows.DependencyObject.GetValue(DependencyProperty dp) at System.Windows.Media.Media3D.MeshGeometry3D.get_Normals() at DispatcherObject.Simulator.AccessMesh() in C:\Users\geraud\source\repos\DispatcherObject\DispatcherObject\Simulator.cs:line 11 at DispatcherObject.MainWindow.b__4_0() in C:\Users\geraud\source\repos\DispatcherObject\DispatcherObject\MainWindow.xaml.cs:line 33 at System.Threading.Tasks.Task.InnerInvoke() at System.Threading.Tasks.Task.Execute()

This exception was originally thrown at this call stack: [External Code] DispatcherObject.Simulator.AccessMesh() in Simulator.cs DispatcherObject.MainWindow.CallSimulatorMethod.AnonymousMethod__4_0() in MainWindow.xaml.cs [External Code]

geriwald
  • 190
  • 1
  • 4
  • 17
  • 4
    Tasks aren't threads. Threads don't own classes or fields. And `The calling thread cannot access this object because a different thread owns it` has nothing with classes and fields at all. In all operating systems, only the UI thread can modify UI elements. There's no way around this. This is *solved* in C# and the TPL by using `async/await`. Perform any parallel/concurrent/asynchronous operations in the background, use `await` to await for those operations to complete without blocking the UI, the update the UI with the new data – Panagiotis Kanavos Feb 08 '21 at 12:16
  • @PanagiotisKanavos mmh, but in my case the shell is not a UI element. I mean, none of its ancestors are in my UI. I'm not using it in anyway in my UI. I don't see anything about UI in the inheritance of the MeshGeometry3D class. Wha tdo you mean by UI element ? – geriwald Feb 08 '21 at 12:20
  • 2
    Do you read from *or* update **any** UI component as part of the process (or the result of the process). I.e. Are you updating a value on a form or in a control? The obvious answer is "yes" because that's how you get this message. I'm just explaining the above comments in a different way that might help you – pinkfloydx33 Feb 08 '21 at 12:30
  • yes thanks, I appreciate that. I will check what i'm doing after the Task call. – geriwald Feb 08 '21 at 12:34
  • @geriwald you haven't posted any task call. There's a `new Task(` that creates a cold task -a task that doesn't run. There's never any reason to do this. Tasks aren't threads, they don't need to "start". They are a *promise* that something will produce a value in the future. Some of them may run on a threadpool thread, some won't. If `Simulator.FindEquilibrium()` calculates data in the background *without* updating the UI, you should use `await Task.Run(()=> Simulator.FindEquilibrium());` Once the task completes you'll be back on the UI thread – Panagiotis Kanavos Feb 08 '21 at 12:45
  • So I checked and I'm not sure I understand your question. I'm updating the UI from the main thread (yeah another thing i don't understand in the previous comment, the task is running in another thread, i can see both threads explicitly in the 'parallel stacks' view of visual studio). Back to your point : i'm not using the 'Shell' member in any of the UI. – geriwald Feb 08 '21 at 12:45
  • @PanagiotisKanavos Yes those are in the ..., sorry. I updated my question. For the moment i just launch the task, when it runs smoothly i will update the UI. For now i have deactivated the UI update completely. – geriwald Feb 08 '21 at 12:47
  • 2
    @geriwald post your code! We can't understand what's going on either except for two facts - a background thread is modifying the UI (the exception proves this)and tasks are used improperly (the `new Task()` proves this). We have no idea what the code, or the task does though – Panagiotis Kanavos Feb 08 '21 at 12:47
  • BTW you haven't posted the exception either, just part of the message. Post the *full exception text* returned by `Exception.ToString()`. This includes the stack trace that shows *which* method threw and which series of calls resulted in the exception. This will tell you where to look for the problem – Panagiotis Kanavos Feb 08 '21 at 12:48
  • My code is huge... – geriwald Feb 08 '21 at 12:48
  • 2
    A [mcve] will be sufficient. – mjwills Feb 08 '21 at 12:49
  • I added the full exception text to the question – geriwald Feb 08 '21 at 12:54
  • @PanagiotisKanavos Thanks, i changed the task call to what you suggested (using async and await) and the result is the same. I edited the question to show those changes. – geriwald Feb 08 '21 at 13:01
  • *// Here I can't access the mesh properties.* -- we can't see code in place of that comment, even something small, so we can't help. – Kit Feb 08 '21 at 13:02
  • @kit : i added the missing code, thanks. – geriwald Feb 08 '21 at 13:04
  • Because you're still trying to modify the UI in `Juliet.GeometryHelper.CutAndCap`. What does that method do? Does it use a UI class? [System.Windows.Media.Media3D.MeshGeometry3D](https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.media3d.meshgeometry3d?view=net-5.0) is a UI class, probably something already displayed in the UI – Panagiotis Kanavos Feb 08 '21 at 13:06
  • I think you touched what i don't understand. What do you mean MeshGeometry3D is a UI class ? It's not displayed in the UI, i use a simplified model for the UI. Both models are separate .3ds files created in rhino. – geriwald Feb 08 '21 at 13:11
  • Just to be clear : can the exception be caused by the fact that a MeshGeometry3D object "is a UI class" , or (as i understand what you're telling me) it is caused by the fact that this particular meshGeometry3D object has a link with the actual UI of my app – geriwald Feb 08 '21 at 13:16
  • 3
    `MeshGeometry3D` inherits from `DispatcherObject` that clearly states: "Only the thread that the Dispatcher was created on may access the DispatcherObject directly". Normally that would be a UI thread, but doesn't have to be. –  Feb 08 '21 at 13:49
  • Thanks @Knoop. I will look into that, and maybe the simpler solution will be to to recreate the mesh each time i call my FindEquilibrium function. – geriwald Feb 08 '21 at 13:52
  • Thanks again for all your help. It was difficult, because everybody assumed it was a UI problem, as it is a common problem with people getting started. However, it was not. My question has been closed, but i think it's not a good idea as future readers could benefit from the answer @Knoop found. So i rewrote the question. If it's still not clear enough, I can rewrite it. Please tell me. – geriwald Feb 09 '21 at 09:55
  • Added a minimal reproducible exemple – geriwald Feb 09 '21 at 10:33
  • The duplicates you found, as well as numerous others, correctly address the exception (the message for which tells you exactly what the problem is). The primary means of addressing the issue is to make sure you use the object in the thread that owns it; this either means fixing the part that creates the object, so it's created where you need it, or fixing the part that uses the object, so it's used where you created it. For objects that inherit `Freezable`, the third option is to freeze the object before use. Note that there are a number of mechanisms available to you to ensure you're ... – Peter Duniho Feb 09 '21 at 22:34
  • ... executing code that uses the object in the correct thread: async/await is the preferred, but you can also use the `Dispatcher` object directly (`Invoke()`, `BeginInvoke()`, `InvokeAsync()`), or capture the `SynchronizationContext` explicitly and use that. – Peter Duniho Feb 09 '21 at 22:38

1 Answers1

1

Thanks to all the commentators that helped me found the answer, and especially @Knoop.

I was trying to access a MeshGeometry3D, that inherits from DispatcherObject, from a different thread that the one it was created on. I got rid of the exception by recreating the object in the worker thread :

Only the thread that the Dispatcher was created on may access the DispatcherObject directly.

I would like to stress the fact that it was not a UI problem : Though the exception doesn't specify anything about UI, it seems to be mainly encountered with UI objects, as the comments on my and all other related questions in Stack Overflow clearly show.

geriwald
  • 190
  • 1
  • 4
  • 17
  • 1
    This is most likely the worst way to deal with the exception, because it implicitly creates a new `Dispatcher` on the thread when you create the `DispatcherObject`. Dispatcher objects are that way for a reason, and as a general rule should only be used in the original dispatcher thread, i.e. the main UI thread. There may be valid exceptions, but a person who correctly identifies such an exception would know what the original `InvalidOperationException` meant in the first place. – Peter Duniho Feb 09 '21 at 23:16