Home Technical Talk

[Maxscript] DotNet DataGridView + MultiThreading = error while garbage collection

interpolator
Offline / Send Message
SimonT interpolator
I'm working on a script and observed a weird behavior and wanted to share it with you guys. I deleted everything which isn't necessary - that's why the structure of the script might seems confuse.

Situation
I use a DotNet DataGridView and fill it via MultiThreading (CSharpUtilities.SynchronizingBackgroundWorker) with data.

Problem
When i start the script and do a manual garbage collection afterwards (enter gc() in the script listener and hit enter), i get this error in some cases:
An unknown error occurred while MAXScript was
performing garbage collection.
If you get this error multiple times, recommend restarting max

Test Results
I found several places in the script which i can change to fix the problem. I can't explain why that is, but i want to share the knowledge with you and maybe you can explain it to me or it helps other people in the future.

1. Copy this code and execute it in 3Ds Max
2. Do a manual garbage collection (via gc() in the script listener)
3. You should get the error message (see above)
(
	pathsArray = #() 
	tasksArray = #()
	threadsArray = #() 
	
	rollout gckick "gckick" width:448 height:400
	(
		dotNetControl lbxResult "System.Windows.Forms.DataGridView" pos:[8,60] width:432 height:336
		
		fn addThread threadArgumentArray = (
		
			--create thread
			myThread = dotnetobject "CSharpUtilities.SynchronizingBackgroundWorker"
			myThread.WorkerSupportsCancellation = true
			functionName = threadArgumentArray[1] --the Array is 2dimensional 1st value=function 2nd value=path for scannings
			dotnet.addEventHandler myThread "DoWork" functionName
			--add threads to management arrays
			append tasksArray threadArgumentArray
			append threadsArray myThread
			--get the thread ID based on the thread array
			tid = tasksArray.count
			--start the thread
			myThread.runWorkerAsync tid
		)

		fn resizeList = (
		
			gckick.height = 600 + 60
			--WORKS: gckick.height = 660
		)
		
		fn addItemImage = ( 

			resizeList()
		)

		on gckick open do (
			
			lbxResult.SelectionMode = (dotNetClass "System.Windows.Forms.DataGridViewSelectionMode").FullRowSelect
			--Add Columns
			imgCol = dotNetObject "System.Windows.Forms.DataGridViewImageColumn"
			imgCol.width = 100
			imgCol.image = myImg
			lbxResult.columns.add imgCol
			
			infoCol = dotNetObject "System.Windows.Forms.DataGridViewTextBoxColumn"
			infoCol.headerText = "Info"
			infoCol.width = (lbxResult.width - 100)
			lbxResult.columns.add infoCol
			
			--ADD Content
			rowID = lbxResult.rows.add()
			lbxResult.rows.item[0].cells.item[1].value = @"D:\Simon\Projekte\3D\egosoft\interiors_rooms_ar_engibay.max"

			--Start a multithreaded function
			addThread #(addItemImage,"")
			--WORKS: addItemImage()
		)
	)
	
	createDialog gckick
)

Workaround #1

(Not really a solution because it will disable the multithreading, but at least you don't get the GC error anymore)

1. Restart 3Ds Max
2. Change the line addThread #(addItemImage,"") to addItemImage() in the on gckick open do (-section.
3. Execute the script
4. When i do a manueal garbage collection now, i DON'T get any error

Workaround #2

1. Restart 3Ds Max (and revert all your previous script-changes if you made any)
2. Change the line gckick.height = 600 + 60 to gckick.height = 660 in the resizeList-function.
3. Execute the script
4. When i do a manueal garbage collection now, i DON'T get any error

I have no idea why the GC doesn't drop an error after the change in the resizeList function. But I'm happy that i found a "solution" and hope this post will help future people. Or someone can explain what's going on here.

And i want note a really good read when you want to work in a performant wa y with the dotnet DataGrid: http://msdn.microsoft.com/en-us/library/ha5xt0d9.aspx

Solution #1

Kind of a solution was to not call the resizeList() function directly from the multi threaded function addItemImage() since this seems to produce problems. I used a timer which is started before i call the multithreaded function and this timer checks a global bool-variable. This variable is set to true by the multithreaded function when it's done. Then the timer reacts to the now true variable, calls resizeList() and deactives itself again.
(
	pathsArray = #() 
	tasksArray = #()
	threadsArray = #() 
	fillListThreadDone = false
	
	rollout gckick "gckick" width:448 height:400
	(
		dotNetControl lbxResult "System.Windows.Forms.DataGridView" pos:[8,60] width:432 height:336
		timer tThreadObserver "Timer" pos:[424,24] width:24 height:24 active:false interval:200
		
		fn addThread threadArgumentArray = (
		
			--create thread
			myThread = dotnetobject "CSharpUtilities.SynchronizingBackgroundWorker"
			myThread.WorkerSupportsCancellation = true
			functionName = threadArgumentArray[1] --the Array is 2dimensional 1st value=function 2nd value=path for scannings
			dotnet.addEventHandler myThread "DoWork" functionName
			--add threads to management arrays
			append tasksArray threadArgumentArray
			append threadsArray myThread
			--get the thread ID based on the thread array
			tid = tasksArray.count
			--start the thread
			myThread.runWorkerAsync tid
		)

		fn resizeList = (
		
			gckick.height = 600 + 60
			--WORKS: gckick.height = 660
		)
		
		fn addItemImage = ( 

			tThreadObserver.active = true
			tThreadObserver.ticks = 0	
		)

		on tThreadObserver tick do (
			
			if (fillListThreadDone) then (
				
				resizeList()
				
				fillListThreadDone = false
				tThreadObserver.active = false
			)
		)		
		
		on gckick open do (
			
			lbxResult.SelectionMode = (dotNetClass "System.Windows.Forms.DataGridViewSelectionMode").FullRowSelect
			--Add Columns
			imgCol = dotNetObject "System.Windows.Forms.DataGridViewImageColumn"
			imgCol.width = 100
			imgCol.image = myImg
			lbxResult.columns.add imgCol
			
			infoCol = dotNetObject "System.Windows.Forms.DataGridViewTextBoxColumn"
			infoCol.headerText = "Info"
			infoCol.width = (lbxResult.width - 100)
			lbxResult.columns.add infoCol
			
			--ADD Content
			rowID = lbxResult.rows.add()
			lbxResult.rows.item[0].cells.item[1].value = @"D:\Simon\Projekte\3D\egosoft\interiors_rooms_ar_engibay.max"

			--Start a multithreaded function
			addThread #(addItemImage,"")
			--WORKS: addItemImage()
		)
	)
	
	createDialog gckick
)
Sign In or Register to comment.