При отладке вывода может оказаться полезным использовать формат шестнадцатеричного дампа, который показывает ASCII-представление байтов в правом столбце. Я использую xxd
для этого и рекомендую вам сделать то же самое. В частности, для шрифтов OpenType этот формат дампа очень удобен для выявления проблем с выравниванием, особенно в заголовке шрифта.
Чтобы понять, что я имею в виду: вот первый раздел одного из ваших дампов:
00000000: 4f54 544f 0009 0008 0003 0001 4346 4620 OTTO........CFF
00000010: 0000 0000 0000 00c2 9c00 0000 0563 6d61 .............cma
00000020: 7000 0000 0000 0000 c2a4 0000 0024 6865 p............$he
00000030: 6164 0000 0000 0000 00c3 8800 0000 3668 ad............6h
00000040: 6865 6100 0000 0000 0001 0000 0000 2468 hea...........$h
00000050: 6d74 7800 0000 0000 0001 2400 0000 006d mtx.......$....m
00000060: 6178 7000 0000 0000 0001 2400 0000 066e axp.......$....n
00000070: 616d 6500 0000 0000 0001 2c00 0000 064f ame.......,....O
00000080: 532f 3200 0000 0000 0001 3400 0000 6470 S/2.......4...dp
00000090: 6f73 7400 0000 0000 0001 c298 0000 0020 ost............
Вы можете видеть, что первая запись tableDirectory для таблицы CFF
- тег ('CFF') ) начинается как последние 4 байта первой строки (4346 4620
, что в ASCII - «CFF»), и это правильное выравнивание. Поскольку длина строки hexdump составляет ровно 16 байтов, а каждая запись в tableDirectory составляет 16 байтов, должным образом выровненный tableDirectory будет иметь теги для каждой таблицы как последние 4 байта строки. Но, как вы можете видеть, у вас есть некоторое смещение ... 'c' (0x63) для 'cmap' находится на один байт слишком далеко вправо, и последующие записи tableDirectory также сдвигаются. Он должен выглядеть примерно так:
00000000: 4f54 544f 0009 0008 0003 0001 4346 4620 OTTO........CFF
00000010: ???? ???? ???? ???? ???? ???? 636d 6170 ............cmap
00000020: ???? ???? ???? ???? ???? ???? 6865 6164 ............head
00000030: ???? ???? ???? ???? ???? ???? 6868 6561 ............hhea
[...]
Так что похоже на некоторые ваших записей в tableDirectory, в конце добавляется дополнительный байт. Поэтому вам нужно выполнить резервное копирование и проверить этот код, чтобы он сначала работал правильно, а затем позаботиться о searchRange, rangeShift и entrySelector (который, предполагая, что ваш шрифт содержит 9 таблиц, как объявлено, должен быть: 0080 0003 0010
(hex)).
Код старше этого - я написал его за некоторое время до выхода .NET 2.0. Концепция очереди производителя / потребителя на намного старше, чем эта:)
Да, этот код безопасен, насколько мне известно, - но это имеет некоторые недостатки:
Идеи, стоящие за кодом, честно говоря, важнее самого кода.
В тот день я узнал, как работает Monitor.Wait / Pulse (и много о потоках в целом) из приведенного выше фрагмента кода и серии статей , из которой он сделан. Поэтому, как говорит Джон, он имеет большое значение и действительно безопасен и применим.
Однако, начиная с .NET 4, в структуре имеется реализация очереди производитель-потребитель . Я только нашел это сам, но до этого момента он делает все, что мне нужно.
Предупреждение: Если вы прочитаете комментарии, вы поймете, что мой ответ неверен:)
В вашем коде возможен тупик .
Представьте себе следующий случай, для ясности, я использовал однопоточный подход, но его должно быть легко преобразовать в многопоточность с помощью сна:
// We create some actions...
object locker = new object();
Action action1 = () => {
lock (locker)
{
System.Threading.Monitor.Wait(locker);
Console.WriteLine("This is action1");
}
};
Action action2 = () => {
lock (locker)
{
System.Threading.Monitor.Wait(locker);
Console.WriteLine("This is action2");
}
};
// ... (stuff happens, etc.)
// Imagine both actions were running
// and there's 0 items in the queue
// And now the producer kicks in...
lock (locker)
{
// This would add a job to the queue
Console.WriteLine("Pulse now!");
System.Threading.Monitor.Pulse(locker);
}
// ... (more stuff)
// and the actions finish now!
Console.WriteLine("Consume action!");
action1(); // Oops... they're locked...
action2();
Пожалуйста, дайте мне знать, если это не так имеет смысл.
Если это подтвердится, то ответ на ваш вопрос: «Нет, это не безопасно»;) Надеюсь, это поможет.
Вы можете сделать что-то вроде следующего фрагмента кода. Он общий и имеет метод постановки нулей в очередь (или любой другой флаг, который вы хотите использовать), чтобы сообщить рабочим потокам о выходе.
Код взят отсюда: http: //www.albahari .com / threading / part4.aspx # _Wait_and_Pulse
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
public class TaskQueue<T> : IDisposable where T : class
{
object locker = new object();
Thread[] workers;
Queue<T> taskQ = new Queue<T>();
public TaskQueue(int workerCount)
{
workers = new Thread[workerCount];
// Create and start a separate thread for each worker
for (int i = 0; i < workerCount; i++)
(workers[i] = new Thread(Consume)).Start();
}
public void Dispose()
{
// Enqueue one null task per worker to make each exit.
foreach (Thread worker in workers) EnqueueTask(null);
foreach (Thread worker in workers) worker.Join();
}
public void EnqueueTask(T task)
{
lock (locker)
{
taskQ.Enqueue(task);
Monitor.PulseAll(locker);
}
}
void Consume()
{
while (true)
{
T task;
lock (locker)
{
while (taskQ.Count == 0) Monitor.Wait(locker);
task = taskQ.Dequeue();
}
if (task == null) return; // This signals our exit
Console.Write(task);
Thread.Sleep(1000); // Simulate time-consuming task
}
}
}
}