Thread safety when handling CTrack in TagData.Message() on button click
-
Hi,
I have a small plugin example of a Tag Plugin and I want to know if I handle the threading correctly, because in my production version , I encountered some freezes, espacially during a running animation or when Redhift Render View was running.Download Plugin:
test_tag_plugin.zipQuick explanation:
- tag plugin with a button and a link field
- button click has to load a sound
- When the button is clicked, I want to alternate between loading a sound into the track specified in the link field (In the official release, I calculate a sound, save it to the hard drive, and load it into the track.)
- If no track exists, one must be created.
- I handle this within the
MessageMethod as usual - and check using
c4d.threading.GeIsMainThread().
Is this the right approach, or are there other factors to consider?
Should I perform the check inside theadd_soundmethod instead,
or would it be better to check usingc4d.GeIsMainThreadAndNoDrawThread?Please bear in mind that I threw this together quickly; you shouldn't expect perfection in the code—such as error handling—as my only concern right now is thread safety.
Code:
""" Simple CTrack Sound Loader """ import c4d import os from c4d import bitmaps, plugins # be sure to use a unique ID obtained from www.plugincafe.com PLUGIN_ID = 123456789 PY_ADD_AUDIO = 1000 PY_TRACK = 1001 class TestTagPlugin(plugins.TagData): def __init__(self): self.current_sound: str | None = None def Init(self, node, isCloneInit): if not isCloneInit: self.InitAttr(node, c4d.BaseList2D, c4d.DescID(PY_TRACK)) node[PY_TRACK] = None return True def Execute(self, tag, doc, op, bt, priority, flags): return c4d.EXECUTIONRESULT_OK @staticmethod def check_track(node, sound_path): """ Checks if there is a track in the audiotrack slot, if not it creates one and puts the sound into this track Has to be called from the MainThread :param node: the tag instance :param sound_path: str - The path to the sound file """ track = node[PY_TRACK] if not track or not track.GetType() == c4d.CTsound: # create special Audio Track obj: c4d.BaseObject = node.GetObject() track = c4d.CTrack(obj, c4d.DescID(c4d.DescLevel(c4d.CTsound, c4d.CTsound, 0))) obj.InsertTrackSorted(track) node[PY_TRACK] = track track.SetName("Audio_Track") node[PY_TRACK][c4d.CID_SOUND_NAME] = "" node[PY_TRACK][c4d.CID_SOUND_NAME] = sound_path def msg_add_audio(self, node) -> bool: """ Loads the audio into the track :param node: the tag instance :return: bool """ was_animation_running = False if c4d.CheckIsRunning(c4d.CHECKISRUNNING_ANIMATIONRUNNING): # save animation state and stop animation was_animation_running = True c4d.CallCommand(12412) if self.current_sound == "base_1.wav": self.current_sound = "base_2.wav" else: self.current_sound = "base_1.wav" path = os.path.join(sample_path, self.current_sound) if not os.path.exists(path): return False self.check_track(node, path) if was_animation_running: # restart stopped Animation c4d.CallCommand(12412) return True def Message(self, node: c4d.GeListNode, type: int, data: object) -> bool: if type == c4d.MSG_DESCRIPTION_COMMAND: if data["id"][0].id == PY_ADD_AUDIO: if c4d.threading.GeIsMainThread(): self.msg_add_audio(node) return True return True # main if __name__ == "__main__": bmp = bitmaps.BaseBitmap() p_dir, file = os.path.split(__file__) fn = os.path.join(p_dir, "res", "icon.tif") bmp.InitWith(fn) sample_path = os.path.join(p_dir, "res", "samples") c4d.plugins.RegisterTagPlugin(id=PLUGIN_ID, str="test_tag_plugin", info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE, description="test_tag_plugin", g=TestTagPlugin, icon=bmp)An additional question regarding the tag:
I’m also curious about a scenario where I launch aGeDialogfrom the tag via a button and pass the tag to it and toggle the sound from within the dialog as well. Would usingc4d.SpecialEventAdd()keep me on the safe side—for instance. So if the dialog runs asynchronously, stores the tag as a member variable,
retrieves the class instance viaGetNodeData, and then calls theadd_soundfunction?
Is that a viable approach? It works in the official plugin.Thanks in advance.
T.B. -
Hello @ThomasB,
could you share your plugin via something like dropbox or google drive with us? You upload above seems to have failed.
Your code looks fine, you do everything correctly at first glance. You only try to modify the scene in the context of
MSG_DESCRIPTION_COMMAND(which should only be invoked from the main thread) and shield yourself against rogue actors by also checkingc4d.threading.GeIsMainThread()(slightly better would beGeIsMainThreadAndNoDrawThreadbut that is just semantics). Your node does also not do anything wildly expensive in itsInit/__init__.But I am sure you are not just imagining your performance issues. Generally speaking, your code should only run when the user clicks the button with the ID
PY_ADD_AUDIOin the description of your tag. So, this should not be able to accidently run when you render or while scene playback (which I assume is what you mean with 'running (an) animation').PY_TRACKseems to be a parameter of typeDTYPE_BASELINKwhere you just link the newly created track. I cannot really see anything going wrong with this, as this is very harmless. I also checked if there is any 'special sauce' in creating sound tracks, as they are one of the special tracks, but as far as I can see, we are doing this internally exactly like you do it. So, this also does not seem to be a case of an incorrectly allocated node, which then constantly causes internal errors.Cheers,
Ferdinand -
Oh sorry, I missed that.
Is Google Drive ok?(which I assume is what you mean with 'running (an) animation').
By "running during animation," I meant clicking the button while the animation is playing.
-
Absolutely, but you have to either grant me access or make the whole folder public.
-
@ferdinand
WTF is wrong with me? It should work now. -
It did, thanks, I will have a look.
-
Hey,
Can you provide a bit more context? I tried your plugin on Cinema 4D 2025.2.1 and macOS 26.5.1. I added your tag to a scene, clicked the "Add Audio" button, and started playback.

- I experience no slowdowns when I run the scene playback (
F8) or the RS render view. - I hear the
base_1.wavsound playing your plugin adds.
Can you please:
- Provide the type and version of operating system you use.
- Provide the version of Cinema 4D you use.
- Describe the hardware you use.
- Provide an example scene.
- Check if you experience the same slowdowns when your plugin is being removed from the scene (but the sound track it has added is being left in.
- If necessary, provide a step-by-step instruction to describe your bug. See here for how to formally make a bug report.
My hunch right now is that your plugin has probably little to do with the slowdowns, but that sound track playback is the culprit which might lead to 'resource fighting' on some systems.
Cheers,
Ferdinand - I experience no slowdowns when I run the scene playback (
-
@ferdinand
First of all, thank you very much for your efforts; where you get such perseverance from is truly remarkable.- Windows 11 Pro 26200.8524
- Cinema 4D 2026.2
- Hardware
- CPU: AMD Ryzen 9 5950X 16-Core
- RAM: 64 GB
- GPU: RTX 3060 (Studio Driver)
I think you misunderstood me. I’m not trying to report a bug here, since the issue isn't with Cinema 4D itself. Rather, I wanted to know if I’m handling the threading correctly here within the Main Thread, because I’ve been experiencing some freezing when I clicked the "Convert" button during playback.
️By the way in the simple plugin example, you can click the Convert Button multiple times. It just toggles back and forth between the two sounds.
I experienced these freezes with the paid plugin that is already on the market. In principle, the convert function is the same, though it’s a bit more complex—calculating and loading audio.
I have the bug report, too, in case that’s of interest. Though I don't think I checked forc4d.threading.GeIsMainThreadAndNoDrawin that instance. The AI has already analyzed the bug report and specifically pointed out the threading issue, so I’m asking here for the best way to handle the threading or whether I’m doing something wrong.I’d rather not share the large plugin here, as it’s a commercial product.
The plugin is way more complex of course. I try not to build puny plugins
. Currently it consists of that tag and a GeDialog—the step sequencer—that can be launched from the tag.
The step sequencer also has a "Convert" button that triggers the same function within the plugin'sNodeData. It works fine.
In the commercial version, I disabled both Convert Buttons the one in the StepSequencer and the one in the tag while the animation is running, just to be on the safe side.
Since you don't see any major issues with the example plugin's
Convertmethod, I don't think the problem lies with the tag's Convert button, but rather with the Step Sequencer's Convert button.
Can I trigger the
Convertmethod from within theGeDialogas well?
In other words:- when a click occurs in the
GeDialog - I send a
c4d.SpecialEventAdd() - catch it in the
GeDialog'sCoreMessage(), - and trigger the tag's convert function.
That should put me in the Main Thread, right?
- Or can I fire it directly from the GeDialog's Command() method?
You can see it in the figure down below.
Theoretically, could I use any of the three methods, or is one better? Or am I not allowed to do that at all?

Anyone who works makes mistakes.Cheers
T.B -
@ThomasB said in Thread safety when handling CTrack in TagData.Message() on button click:
I think you misunderstood me. I’m not trying to report a bug here, since the issue isn't with Cinema 4D itself.
No, I did not misunderstood your situation (at least I think so
). We sometimes ask people to report a formal bug report so that we can reproduce an issue. This is not necessarily bound to the bug being in Cinema 4D. Although we often do this when we suspect that the bug is on the Cinema side or at least in a grey area where the plugin does something that is not so great but Cinema also drops the ball in some shape or form.Your code, at least the one you shared with us, does not do anything wrong. And even though I know you have put a lot of work in your reply here, a lot of text and diagrams do not help in concretely fixing the issue. To answer a few questions anyway:
NodeData::Messageusually runs on the main thread (and you additionally check), so we are fine.- Dialog code also usually runs on the main thread. The only exception can be drawing code (something like
GeUseraArea::DrawMsg), although drawing code usually also runs in a special section of the main thread. Hence my hint to useGeIsMainThreadAndNoDrawThreadinstead ofGeIsMainThreadin my first posting. - Generally there is no guarantee that any code runs on the MT. As an example,
NodeData::Messagecalls are usually on the MT, especially the messageMSG_DESCRIPTION_COMMAND. I.e., when a user clicked a button in your node this should run on the MT as you often want to do GUI things or modify the scene. And Cinema will (should) honor this. But nothing prevents me, Evil Bob the developer, from grabbing your tag, creating a new thread then callingyourTag.Message(MSG_DESCRIPTION_COMMAND, BaseContainer())from this new thread and with that violate the assumption that description commands alwary run on the MT. - Core messages are also usually sent from the MT, both in a dialog and in a
MessageData; soCoreMessagefunctions should usually run on the MT. Core message (i.e., event) evaluation is also decoupled. When you set a (core) event from a non-MT thread (viaSpecialEventAdd), Cinema should properly synchronize and execute the event on the MT. But there could be of course bugs in our code. You should therefore always shield your code withGeIsMainThreadAndNoDrawThreadorGeIsMainThreadif your code could also run in a drawing context.
It is good that you have an eye on the threading safety in Cinema as this is something that often trips developers. But I do not think that threading is here the issue. Threading violations usually lead to crashes, corrupted data, or inexplicable behavior. Slowdowns usually mean that either code runs way more often that you think, e.g. some feedback loop between your tag and dialog, or that there is some resource fighting going on. Playing audio means that hardware resources must be allocated. When they are constantly dropped and reallocated, because something else wants to access them too, this can lead to slowdowns. This is still just a guess into the blue of mine, but it would fit the symptoms you are describing.
Could you please:
- Confirm that the issue also happens for your with the simplified plugin.
- Provide a step by step bug report to reproducing the issue as described here.
- Check that the issue does not happen when you delete your tag (but leave the sound track in the scene). I really want to rule out that Cinema does not have a problem with audio playback on some hardware in general.
When push comes to shove, you can also share your full plugin in private with us via sdk_support(at)maxon(dot)com. When you can reproduce the issue with the simplified plugin, this is of course sufficient for us to investigate the issue. But if you cannot reproduce the issue with the simplified plugin, then we would need to see the full plugin to understand what is going on.
Cheers,
Ferdinandedit: I now also tried on my Win 11 machine (Intel chipset, a bit beefier GPU). And I cannot detect any slowdowns in normal scene playback or when using the RS render view. However, when I use the Calculate Fps tool (just press
SHIFT + Cand type'FPS'), I experience distorted audio and the view port becomes sightly laggy. This happens with and without your tag, an audio track alone seem to be enough.