MultiThreading in 3D Studio Max using the BackgroundWorker class

Aug 29, 2008 by     10 Comments    Posted under: 3dsMax, DotNet, Maxscript

You will all be aware that when you perform any intensive calculation process within MaxScript that it pretty much ties up that session of max. The solution to this would be to run this process in a separate thread, much like 3DS Max does with all other operations. However, the system.threading class can be tricky to configure. Luckily the DotNet chaps have provided an alternative that does all the hard work for you – The BackgroundWorker.

You create an instance of this class in MXS via the following method –

MainThread = dotnetobject "System.ComponentModel.BackGroundWorker"

This is a small test that demonstrates use of this class. You will notice that when you run the example via MXS, the 3Dsmax UI will be completely tied up. Via the Dotnet method, you can still use the max interface and do other tasks within a single copy of max. It also supports cancellation so you can abort an intensive task should you need to.

The main thread is called via the DoWork() event – specifying this in MaxScript as a dotnethandler allows you to pass a function to operate as the work thread. So you can pretty much put what you like in here and it will run in a thread separate to the UI. This is obviously better suited to work that isn’t viewport related. It is perfect if you want to perform an intensive calculation without starting a separate instance of 3DS Max.

Updating the UI thread from the Worker Thread

Something to understand in VB/C# with this class is that like using other threading methods you have to use delegates if you want to update a UI that was started in a thread different to the one perform the work. If you attempt this in VB/C# it will throw an exception. The backgroundworker class has this delegate functionality built in, providing an easy way to pass progress (and other) information back to the UI thread. I haven’t implemented it here as it didn’t seem to be necessary for the script to work, although i have included it commented out in the code. In VB/C#, The backgroundworker class allows this to happen via the ProgressChanged handler. If you look in the script this can only report back if you set the object’s workerreportsprogress property to true., without this property set will also result in an exception.

Once started with the runworkerasync() function, The mainthread performs the work function. You choose when to update back to the UI thread via the reportprogress method. This takes two arguments, a percentage integer and a userstate object. So, you can put pretty much anything in this object – i have placed an array with various properties in so i can can the userstate[integer]method to retrieve these in the updatethread function. The ProgressChangedEventArgs therefore contains a progresspercentage property to update a progressbar or similar and the userstate property for anything else. For example I have used this to pass the e.progresspercentage argument to the painthandler of a control and use GDI+ to draw a custom progressbar over a bitmap that is loading. You don’t have to provide a userstate property, the eventarg is overloaded (meaning you can choose which arguments to supply) so that you can just return the progress percentage should you wish.

Insert Update Thread pun here…

Whilst the background worker seems to perform well in a VS environment, I had notived a couple of intermittent synchronisation errors when using a this class within 3dsMax. Following a discussion on CGTalk, the dotnet SDK mentions this issue. Here is what it says –

SynchronizingBackgroundWorker Class

Replaces the standard BackgroundWorker provided by the .NET Framework to fix what seems to be a bug causing the BackgroundWorker to fire events in the wrong thread.We have encountered cases where the BackgroundWorker seems to lose track of the main thread or just arbitrarily decide to fire ProgressChanged and RunWorkerCompleted in a new thread rather than in the main thread. This causes synchronization errors.

This replacement class allows the client to specify a ISynchronizeInvoke synchronizer object through which the events will be fired. All Controls implement ISynchronizeInvoke, so any Control should be adequate to act as a synchronizer to invoke the events in the main thread.

I have updated these classes to run with this component by adding a reference to CsharpUtilities in VS. In max, you can just reference it normally like so –

Worker = DotNetObject "CSharpUtilities.SynchronizingBackgroundWorker"

You don’t need to load the assembly as MaxDoes this automatically at startup.

download script

10 Comments + Add Comment

  • avatar

    Unfortunately the line “e.cancel ” makes nothing. The “exit” construct makes it working and really canceling the loop (after that executes the rest of the function ) in the WorkThread function.
    It seems the scope of the main studio max thread and the the BackGroundWorker’s scope are not communicative. Therefore a dotnet form can be created in the worker’s doWork body, but not loaded or closed.. The problem is resolved by using the “ProgressChanged” EventArg and the form.close() , form.show() … and other problemathick cruces inside its function.
    Greetings

  • avatar

    Further to my comments above, I now think that you cannot use any modify panel setting command in the second thread.

    Unfortunately doing any Edit Poly modifier operations in a worker thread most likely will require the use of the “modPanel.setCurrentObject” command and I have found that it gives unpredictable results. Usually it leaves the modify panel in a strange non-operative state where buttons strangely appear as you move your mouse over the panel.

    My conclusion at the moment is that modify panel commands (and possibly other commands too) are not thread safe and therefore cannot be reliably used in a worker thread.

    Despite being very familiar with threads in C++ unfortunately dotNet is new territory for me so I may be missing something here. If anyone has an alternative solution I would be very happy to hear it.

    Thanks.

  • avatar

    Thanks again for this fine article.

    However, I have found when using this method that if you change the command mode to modify mode within the thread then Max will freeze once the thread is finished. At least that’s what happens on my setup.

    For me this was a problem because when operating on modifiers such as Edit Poly you need to switch to modify mode using something like “max modify mode”.

    The workaround to this is to change to modify mode in the main thread and NOT in the worker, and then it won’t freeze.

    To show you how to make Max freeze simply replace your WorkThread() function with the following. Be warned though, 3ds Max will freeze when you press the cancel button to exit the thread and you’ll have to use ctrl-alt-del to invoke the task manger to close Max. If you have unsaved work you may lose it.

    Fn WorkThread sender e =
    (
    — Warning!!!!! Using “max modify mode” in the
    — following line will cause Max to freeze once
    — the thread is finished.
    max modify mode — This is the culprit.
    local lvObj = Box()
    select lvObj

    — Loop forever until user requests a cancellation.
    while not MainThread.CancellationPending do
    (
    Thread.pb2.value += 1
    if Thread.pb2.value >= 100 do
    Thread.pb2.value = 0
    sleep 0.1
    )

    — Finished, display status.
    Thread.lbl2.text = “Primary Thread Complete”
    Thread.pb2.value = 0
    )

  • avatar

    Wow! Will definitely be making plenty of use of this, thanks for providing it!

  • avatar

    You’re the man! Thanks for the download, it’s working like a charm. I sure will have a lot of use for this. 🙂

    Thank you again!

  • avatar

    Hi Spongebob,

    Thanks for the extra update and your interest.

  • avatar

    Thank you for this article, it is a saviour.

    I actually come up against this very problem while doing a lengthy script and I soon realised that I needed a second thread.

    I had a feeling that I could do it using dotNet but not being familiar with that I took the easy way out and posed the question on the Area. Fortunately I got a response with a link to this page, and my script now works beautifully without “appearing” to lock up at all.

    One thing though, in your example if you close the dialog box using Window’s close button or Alt+F4 then the threads do not cancel. However adding the following close handler fixes it.

    on Thread close do
    (
    if MainThread.IsBusy do MainThread.CancelAsync()
    if SecondaryThread.IsBusy do SecondaryThread.CancelAsync()
    )

    Regards,
    Spongebob

  • avatar

    I finally had some time to try out the backgroundWorker class, and (after a few “OH GOD HOW THE HELL AM I SUPPOSED TO WORK WITH THREADS” moments) I must say I can see a ton of applications for it. Thank you for the excellent tutorial and info.

    -M.

  • avatar

    Hi Loocas! I have added the download to the thread now, please let me know if there’s anything else. regards, Pete

  • avatar

    Hi,

    is the script downloadable somewhere? Would be really helpful to see how you went about it.

    Thanks a lot!

    – loocas

Got anything to say? Go ahead and leave a comment!

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>