Recently, I was refactoring some code to improve performance and converted it from single-threaded to multi-threaded.

The code was in the form of a Processor that injected an Image in its constructor, and then used it internally for some operations.

The Processor was as follows:

public class ImageProcessor
{
	private readonly Bitmap _img;
	public ImageProcessor(Bitmap img)
	{
		_img = img;
	}

	public void Process()
	{
		using (var g = Graphics.FromImage(_img))
		{
			g.Clear(Color.Wheat);
		}
	}
}

The new code invoked this as follows:

async Task Main()
{
	const int taskCount = 10;

	var img = new Bitmap(500, 500);

	// Create an array of tasks
	Task[] tasks = new Task[taskCount];

	for (int i = 0; i < taskCount; i++)
	{
		tasks[i] = Task.Run(() =>
		{
			var processor = new ImageProcessor(img);
			processor.Process();

			Console.WriteLine($"Task {Task.CurrentId} done.");
		});
	}

	// Wait for all tasks to complete
	await Task.WhenAll(tasks);

	Console.WriteLine("All tasks finished.");
}

This code, when run, inevitably threw an exception:

objectInUser

The stacktrace is as follows:

at System.Drawing.Graphics.FromImage(Image image)
   at UserQuery.ImageProcessor.Process() in C:\Users\rad\AppData\Local\Temp\LINQPad5\_xjstuxvs\query_bpcpjn.cs:line 81
   at UserQuery.<>c__DisplayClass0_0.<Main>b__0() in C:\Users\rad\AppData\Local\Temp\LINQPad5\_xjstuxvs\query_bpcpjn.cs:line 53
   at System.Threading.Tasks.Task.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at UserQuery.<Main>d__0.MoveNext() in C:\Users\rad\AppData\Local\Temp\LINQPad5\_xjstuxvs\query_bpcpjn.cs:line 60

Now you might say, of course, you’d get such an exception - the Processor is modifying the passed instance of the image.

To which my response is this is just a simplistic example - the actual scenario was that the Processor was a reporting tool to which I was passing an Image for display, and I had no visibility into how the Image was being used internally.

For the exception, it is clear that a Graphics object in use internally was the root of my problems.

The solution was simple - don’t pass the Image at all.

Pass a byte array instead, and have the consumer be responsible for re-constituting the image.

The new Processor would look like this:

public class ImageProcessor2
{
	private readonly byte[] _img;

	public ImageProcessor2(byte[] img)
	{
		_img = img;
	}

	public void Process()
	{
		using (var ms = new MemoryStream(_img))
		{
			using (var bmp = new Bitmap(ms))
			{
				// use the bitmap in here
			}
		}
	}
}

Here, the Graphics object (or whatever equivalent) internal to the processor is created per instance.

We also have the added benefit of being able to safely dispose of the Graphics object once we’re done with it, since it implements IDisposable.

TLDR

Passing around Image objects will likely land you in problems, due to threading and Graphics issues.

Pass the data instead and have the consumers reconstitute the Image when needed.

Happy hacking!