1

I've read through a lot of the SO questions related to BackgroundWorker and DispatcherTimer and understand that you can't access UI components on any thread other than the main thread.

So I have DispatcherTimer that ticks every 1/2 sec. As you would expect I can update the view model class and any UI element that needs direct manipulation and the UI is pretty responsive. However I have a calculation to do based on a value on the UI that takes about 3 secs to run.

I tried just doing the calulation in the DispatcherTimer thread and that locked/blocked the UI until it completed. At present I have a check in the DispatcherTimer that then triggers a BackgroundWorker thread to go off and do the calculation. I use e.Arguments to pass my 3 sec compute process the data it needs and e.Result for the completed data I get back.

I've checked the result and there are no errors generated. However when I cast the e.Result back into my class, the e.Result is not able to evaluate properly. I essentially get a "calling thread cannot access error" when I come to use the class property.

...
timerBackgroundLoop = new DispatcherTimer(DispatcherPriority.Background);
timerBackgroundLoop.Interval = TimeSpan.FromMilliseconds(500);
timerBackgroundLoop.Tick += new EventHandler(Timer_Tick);
timerBackgroundLoop.Start();
...

private void Timer_Tick(object sender, EventArgs e)
{
    if (MyClass.NeedToRebuildBuildMap)
    {
        MyClass.NeedToRebuildBuildMap = false; //stop next timer tick from getting here
        threadDrawMap = new BackgroundWorker();
        threadDrawMap.WorkerReportsProgress = false;
        threadDrawMap.WorkerSupportsCancellation = false;
        threadDrawMap.DoWork += new DoWorkEventHandler(threadDrawMap_DoWork);
        threadDrawMap.RunWorkerCompleted += new RunWorkerCompletedEventHandler(threadDrawMap_Completed);
        threadDrawMap.RunWorkerAsync(myClass.MapData);
    }
    ...
}

private void threadDrawMap_DoWork(object sender, DoWorkEventArgs e)
{
    MyClass.MapData _mapData = (MyClass.MapData)e.Argument;                        
    e.Result  = BuildMap(_mapData);
}

private void threadDrawMap_Completed(object sender, RunWorkerCompletedEventArgs e)
{
    MyClass.MapGeo = (List<PathGeometry>)e.Result;
    DrawUIMap(MyClass.MapGeo); //draws the map on the UI into a grid
}

When I set a break in "threadDrawMap_Completed" and evaluate the List of PathGeometry I get this error: base {System.SystemException} = {"The calling thread cannot access this object because a different thread owns it."}

I don't actually see an error until when I'm in the DrawUIMap method and I try and access the MyClass.MapGeo geometry list. At this point tho I'm back on the DispatcherTimer thread, which can access the UI.

As far as I know I've done everything right as far as where/when I access UI components. Although I figure I've done something horribly wrong somewhere.

**Edit: This is part of the code that does the calulation

public static List<PathGeometry> BuildMap(List<PathGeometry> _geoList)
{
    ...
    List<PathGeometry> retGeoList = new List<PathGeometry>();
    retGeoList.Add(geoPath1);        
    retGeoList.Add(geoPath2);
    ...
    return retGeoList;
}
MikeyTT
  • 222
  • 3
  • 11
  • where do you reset `MyClass.NeedToRebuildBuildMap`, I'm not sure, but use a flag in the `timer` to prevent next `BackgroundWoker` from running may not be the best option? there may be a chance that the flag get reset, and before it be set to `False`, two `Timer tick` kicked in.. – Bolu Oct 09 '13 at 16:32
  • Sorry just seen your comment. No is the answer in this case, I double checked that it would only get called the one time, hence moving the bool flag around. I actually set the flag elsewhere in the code based on a menu item click, which as soon as the user has essentially asked for the MapRedraw I hide it and show a busy indicator until the map has redrawn. It's more elegant than I've made it sound here actually ;-) – MikeyTT Oct 09 '13 at 17:34

1 Answers1

0

That object is owned by the ThreadPool thread that created it.

If you call Freeze(), it should become usable by other threads.

Harrison
  • 3,843
  • 7
  • 22
  • 49
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Which object should be frozen? I have looked at other parts of my app that do similar things, which don't have any issues, and I can't see any difference with this. I've never used Freeze on any object before. I'll look into that in the meantime. – MikeyTT Oct 09 '13 at 16:37
  • @MikeyTT: Probably the `PathGeometry`s. This is only a problem if you create the object on one thread, then use it on a different one. – SLaks Oct 09 '13 at 16:41
  • So I checked and double checked my other code and I have almost exactly the same bits of code elsewhere that do not have this issue. The only difference is that I have never used a PathGeometry before. If I have a List or an ObservableCollection of another class I never hit this issue, even if I call "new...();" on it. Seems like it's something related to PathGeometry. Either way as soon as I Freeze the object then I can access it. I'm sure I'll hit issue now I'm working with a Frozen object, but hey, baby steps. Thanks for the pointer... – MikeyTT Oct 09 '13 at 17:30
  • @MikeyTT: This only applies to objects that have thread affinity (anything that inherits `DispatcherObject`). Ordinary collections won't throw this error. Note that they still aren't thread-safe; they just don't give you an upfront error about it. Be careful. – SLaks Oct 09 '13 at 17:32
  • Careful? you've never seen my real code. You would have nightmares I'm sure ;-) Nice to know tho, I've probably been a little willy-nilly about these things TBH. I don't code for a living, just a hobbyist really. – MikeyTT Oct 09 '13 at 17:36