I'm getting a crash in my code when generating a datamatrix for a label. After a lot of testing, I've determined it's due to multi-threading, however I cannot determine why.
I'm using iTextSharp v5.5.13.2(Nuget package). Code that will replicate the error is below:
for(int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(x =>
{
iTextSharp.text.pdf.BarcodeDatamatrix dataMatrix = new iTextSharp.text.pdf.BarcodeDatamatrix();
dataMatrix.Height = 18;
dataMatrix.Width = 18;
dataMatrix.ForceSquareSize = true;
dataMatrix.Generate("TestData");
});
Console.WriteLine(i);
}
This is the error:
System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'
This exception was originally thrown at this call stack:
iTextSharp.text.pdf.BarcodeDatamatrix.B256Encodation(byte[], int, int, byte[], int, int, int, int, int)
iTextSharp.text.pdf.BarcodeDatamatrix.GetEncodation(byte[], int, int, byte[], int, int, int, bool)
iTextSharp.text.pdf.BarcodeDatamatrix.Generate(byte[], int, int)
iTextSharp.text.pdf.BarcodeDatamatrix.Generate(string)
DataAccessTesting.Form1.Function1_Click.AnonymousMethod__59_0(object) in Form1.cs
System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(object)
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, object, bool)
System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, object, bool)
System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
System.Threading.ThreadPoolWorkQueue.Dispatch()
...
[Call Stack Truncated]
If I remove the multithreading, it runs perfectly fine. So this code works:
for (int i = 0; i < 10; i++)
{
iTextSharp.text.pdf.BarcodeDatamatrix dataMatrix = new iTextSharp.text.pdf.BarcodeDatamatrix();
dataMatrix.Height = 18;
dataMatrix.Width = 18;
dataMatrix.ForceSquareSize = true;
dataMatrix.Generate("TestData");
Console.WriteLine(i);
}
I'm using multi threading in the application to generate all the labels and immediately start printing them, even while other labels are still still generating. Forcing them to wait until each of the others has finished processing will significantly slow down the application, so removing the multithreading is a last resort option.
How can I prevent this error from occurring without removing multi threading?
EDIT:
I keep getting told I don't have the complete code. I tested again, and by putting the code I added into a button event, I can replicate the error. Here's a screenshot.
Decompiling the BarcodeDatamatrix
in the library confirms that the Generate
method is not thread-safe, due to the usage of the following fields (thanks @Enigmativity for the insightful comment):
private static int[][] f;
private static int[][] switchMode;
The class at the following location makes these fields (as well as other methods that use them) not static
, fixing the thread-safety issue. Important: if the library/NuGet (actually this class) gets updated, this may need to be done again should you want to use the latest version:
https://pastebin.com/PFnqBYQa
Alternative solution
Use the DataMatrix.net
library available here or here (old library, not sure which one is better).
Additional information
I also tested ZXing.NET but FNC1 characters (ex. group separator \x1D
) were not working properly. This works fine with the above library.
Code samples (for alternative libraries):
// ZXING: FNC1 not working
//var dm = new BarcodeWriter
//{
// Format = BarcodeFormat.DATA_MATRIX,
// Options =
// {
// GS1Format = true,
// PureBarcode = true
// }
//};
//var gs1Code = "0107612345678900171" + "\x1D" + "00503";
//var bmp = dm.Write(gs1Code);
//return bmp;
// Datamatrix.NET
var imageEncoder = new DmtxImageEncoder();
var options = new DmtxImageEncoderOptions
{
ModuleSize = 8,
MarginSize = 30,
BackColor = Color.White,
ForeColor = Color.Black,
Scheme = DmtxScheme.DmtxSchemeAsciiGS1
};
var barcode = txt1.Text.Length == 13
? "0" + txt1.Text
: txt1.Text;
return imageEncoder.EncodeImage("01" + barcode
+ "10" + txt2.Text + "\x1D"
+ "21" + txt3.Text
, options);