<p>В модуле <code>OpenGL</code> так же есть особые классы, <code>wgl</code>, <code>gdi</code> и <code>glx</code>:</p>
<ul>
<li><p><code>gdi</code> содержит несколько методов библиотеки <code>gdi32.dll</code>. На этой библиотеке основано всё в <code>System.Windows.Forms</code>.<br/>
Подпрограммы включённые в класс <code>gdi</code> - это то, что может понадобиться вам, чтоб настроить и подготовить форму для рисования на ней с помощью OpenGL.</p>
Подпрограммы включённые в класс <code>gdi</code> - это то, что может понадобиться вам, чтобы настроить и подготовить форму для рисования на ней с помощью OpenGL.</p>
</li>
<li><p><code>wgl</code> содержит методы для подключения OpenGL к окну Windows.</p>
</li>
@ -1245,7 +1245,7 @@ var v2: Vec3i;
(v1-v2).Println;
</code></pre>
<p>Чтоб применить 1 из этих операций к 2 векторам - их типы должны быть одинаковые.<br/>
Если это не так - 1 из них (или оба) надо явно преобразовать, так чтоб типы были одинаковые:</p>
Если это не так - 1 из них (или оба) надо явно преобразовать, так чтобы типы были одинаковые:</p>
<pre><code>var v1: Vec3d;
var v2: Vec2i;
...
@ -1475,7 +1475,7 @@ m.Println; // Вывод матрицы
// s присвоит ту же строку, что выводит .Println
var s := m.ToString;
</code></pre>
<p>Для того чтоб матрица выведенная 1 из этих методов выглядела красиво надо
<p>Для того чтобы матрица выведенная 1 из этих методов выглядела красиво надо
использовать моноширный шрифт и поддерживать юникод (потому что для матриц
используются символы псевдографики).</p>
<p>Обычно это не проблема для <code>.Println</code>, потому что и консоль, и окно вывода в IDE имеют моноширный шрифт и поддерживают юникод.</p>
// Компилятор развернёт это в "FillBuffer(data)"
// То есть никакие преобразования в .exe не попадут
// Но указатели всё равно нужны, чтоб компилятор не ругался на несовместимость типов
// Но указатели всё равно нужны, чтобы компилятор не ругался на несовместимость типов
FillBuffer(PByte(pointer(@data))^);
end;
@ -1824,7 +1824,7 @@ end;
<p>Но если вы, к примеру, создаёте много OpenGL шейдеров из исходников - можно перед компиляцией программы:</p>
<ol>
<li>Прочитать все текстовые файлы исходников шейдеров;</li>
<li>Использовать <code>Marshal.StringToHGlobalAnsi</code> чтоб получить неуправляемые строки;</li>
<li>Использовать <code>Marshal.StringToHGlobalAnsi</code> чтобы получить неуправляемые строки;</li>
<li>Пересохранить их в бинарном виде (то есть как массив байт содержимого неуправляемой строки);</li>
<li>Полученные бинарные файлы подключать в виде <code>$resource</code>, читать как массив байт и его уже передавать неуправляемому коду вместо строки.</li>
<p>OpenCL это неуправляемая библиотека. Обычно можно заставить управляеммые типы данных работать с ней.<br/>
Но часто это приводит к дополнительным затратам производительности.<br/>
И обычно не значит всегда - <code>MemorySegment.ReadValue</code>, принимающее запись <code>var</code>-параметром не может быть безопасным из за сборщика мусора
(и поэтому отсутствует).</p>
<p>Более прямым будет передача неуправляемых типов - указателей - без преобразований в подпрограммы модуля <code>OpenCL</code>.<br/>
И эта возможность тоже существует, к примеру в виде <code>MemorySegment.WriteData</code>. Но такие указатели ещё более не_безопасны:<br/>
Как минимум они требуют освобождения в <code>try-finally</code> чтобы избежать утечек памяти.
И защиты от дурака, не возволяющей записать значение типа <code>real</code> туда, где хранится <code>int64</code> - не существует.</p>
<p>Как что-то среднее между этими двумя вариантами - существует <code>NativeValue<T></code>:<br/>
Этот класс является обёрткой указателя. Именно обычного указателя - не памяти <code>GPU</code> как все остальные простые обёртки <code>OpenCLABC</code>.</p>
<pre><code>## uses OpenCLABC;
// В конструктор необходимо передавать значение,
// потому что иначе неуправляемая память будет содержать мусор
// Но можно передать default, чтобы заполнить выделяемую память нулями
var nv := new NativeValue<integer>(default(integer));
nv.Value := 5; // Значение можно и читать,
nv.Value.Println; // и перезаписывать
// Напрямую получать доступ к области памяти,
// через свойство Pointer, не рекомендуется
Writeln(nv.Pointer);
nv.Dispose; // Освобождение памяти - вызывается и само при сборке мусора
<p>Методы, запускающие <code>Kernel</code> принимают специальные аргументы типа <code>KernelArg</code>, которые передаются в OpenCL-C код.<br/>
Экземпляр <code>KernelArg</code> может быть создан из нескольких типов значений, а точнее:</p>
<pre><code>## uses OpenCLABC;
@ -1173,6 +1205,7 @@ ks.PrintLines;
var k: Kernel;
var val1 := 3;
var val2 := 5;
var val3 := new NativeValue<byte>(7);
var a: array of byte;
k.Exec1(1,
@ -1198,6 +1231,10 @@ k.Exec1(1,
val1,
HFQ(()->val1),
// В том числе неуправляемое
val3,
HFQ(()->val3),
// Массив размерных значений
a,
HFQ(()->a)
@ -1248,29 +1285,26 @@ begin end.
</li>
<li><p><code>x</code> не должно быть захвачего лямбдой.<br/>
Хотя указатель на <code>x</code> уже можно захватывать:</p>
<pre><code>uses OpenCLABC;
<pre><code>## uses OpenCLABC;
begin
var k: Kernel;
var val1 := 3;
var k: Kernel;
var val1 := 3;
// На val1 всё ещё накладываются все ограничени,
// когда val1_ptr использована в качестве KernelArg
// Но к самой val1_ptr эти ограничения не применяются
var val1_ptr := @val1;
// На val1 всё ещё накладываются все ограничени,
// когда val1_ptr использована в качестве KernelArg
// Но к самой val1_ptr эти ограничения не применяются
var val1_ptr := @val1;
k.Exec1(1,
k.Exec1(1,
val1,
HFQ(()->val1_ptr^), // захватили переменную val1_ptr, а не val1
val1,
HFQ(()->val1_ptr^), // захватили переменную val1_ptr, а не val1
// val1 нигде не захвачена, поэтому теперь так можно
@val1,
val1_ptr // то же самое
// val1 нигде не захвачена, поэтому теперь так можно
@val1,
val1_ptr // то же самое
);
end.
);
</code></pre>
</li>
<li><p>Выходить из подпрограммы, где объявили <code>x</code> нельзя, пока <code>.Exec</code> не закончит выполнятся.
@ -1321,9 +1355,9 @@ end.
часть кода, или различие архитектур компьтеров, таким образом усложняя поиск источника ошибки.</p>
<p>Поэтому, ещё раз, используйте передачу адреса в качестве <code>KernelArg</code> только как тонкую оптимизацию и только когда понимаете что делаете.</p>
<p>Обычные программы невозможно запустить на GPU. Для этого надо писать особые программы.<br/>
В контексте OpenCL - эти программы обычно пишутся на языке "OpenCL C" (основанном на языке "C").</p>
<p>Язык OpenCL-C это часть библиотеки OpenCL, поэтому его справку можно найти <ahref="https://www.khronos.org/registry/OpenCL/">там же</a>, где и справку OpenCL.</p>
<divid="page-16" page_name="Создание из бинарного файла"hidden=true>
<p>После создания объекта типа <code>ProgramCode</code> из исходников можно вызвать
метод <code>ProgramCode.SerializeTo</code>, чтобы сохранить код в бинарном и прекомпилированном виде.
Обычно это делается отдельной программой (не той же самой, которая будет использовать этот бинарный код).</p>
@ -1357,13 +1391,13 @@ end.
используя статический метод <code>ProgramCode.DeserializeFrom</code>.</p>
<p>Пример можно найти в папке <code>Прекомпиляция ProgramCode</code> или <ahref="https://github.com/SunSerega/POCGL/tree/master/Samples/OpenCLABC/%D0%9F%D1%80%D0%B5%D0%BA%D0%BE%D0%BC%D0%BF%D0%B8%D0%BB%D1%8F%D1%86%D0%B8%D1%8F%20ProgramCode">тут</a>.</p>
<p>Все типы очередей наследует от <code>CommandQueueBase</code>. Это значит что любую очередь можно сохранить в переменную типа <code>CommandQueueBase</code>.<br/>
Ноо значении типа <code>CommandQueueBase</code> известно не на много больше чем о значении типа <code>object</code>.</p>
<p>Так же, все очереди наследуют от одного из двух типов:</p>
<ol>
<li><code>CommandQueueNil</code> - очередь возващающая <code>nil</code> (именно нулевую ссылку, не пустое значение любого типа).</li>
<li><code>CommandQueue<T></code> (где <code>T</code> - любой тип) - очередь возвращающая значение типа <code>T</code>;</li>
</ol>
<p>После выполнения очереди <code>CommandQueue<T></code> метод <code>Context.SyncInvoke</code> возвращает то, что вернула очередь.<br/>
А если использовать метод <code>Context.BeginInvoke</code> - возвращаемое значение можно получить с помощью метода <code>CLTask<T>.WaitRes</code>.</p>
<hr/>
<p>Результат других типов очередей нельзя получить, но их можно преобразовать к <code>CommandQueue<T></code>с произвольным <code>T</code>с помощью <code>.Cast</code>:</p>
<pre><code>## uses OpenCLABC;
// Q объявлена как CommandQueueBase,
// а значит в неё можно сохранить любую очередь
var Q: CommandQueueBase;
// В данном случае сохраняем CommandQueueNil
Q := HPQ(()->Writeln('Q выполнилась'));
// Преобразовывать nil можно в любой ссылочный тип
<p>Очереди, созданные из областей памяти или kernel'ов возващают свои области памяти/<code>Kernel</code>'ы соответственно, из которых были созданы;<br/>
<p>Очереди, созданные из областей памяти OpenCL или kernel'ов возващают свои области памяти/<code>Kernel</code>'ы соответственно, из которых были созданы;<br/>
Очереди, созданные с<code>HFQ</code> - значение, которое вернёт переданная функция;<br/>
Очереди, созданные с<code>HPQ</code> - возвращает <code>nil</code> без типа.</p>
<p>После выполнения очереди метод <code>Context.SyncInvoke</code> возвращает то, что вернула очередь.<br/>
А если использовать метод <code>Context.BeginInvoke</code> - возвращаемое значение можно получить с помощью метода <code>CLTask.WaitRes</code>.</p>
<hr/>
<p>Бывает необходимо хранить несколько очередей, с разными возвращаемыми значениями, вместе. К примеру, в переменной типа <code>List<></code>.<br/>
Но в переменной типа <code>CommandQueue<SomeT></code> можно хранить только очередь с конкретным типом возвращаемого значения <code>SomeT</code>.</p>
<p>Для того, чтобы хранить очереди с разными возвращаемыми значениями в одной переменной - используется <code>CommandQueueBase</code>.<br/>
<code>CommandQueueBase</code> это особый тип очереди, у которого не указывается возвращаемое значение.<br/>
От него наследует <code>CommandQueue<></code>, поэтому переменной типа <code>CommandQueueBase</code> можно присвоить любую очередь.</p>
<p>Если попытаться выполнить очередь типа <code>CommandQueueBase</code>,
или применять операции преобразования очередей, как <code>.ThenConvert</code>,
то возвращаемое значение будет игнорироваться.</p>
</div>
<script>on_start_folder("Возвращаемое значение очередей",document.getElementById("page-17"))</script>
<script>on_end_folder()</script>
<divid="page-18"page_name=""hidden=true>
<p>Самый простой способ выполнить очередь - вызвать метод <code>Context.SyncInvoke</code>.<br/>
Он синхронно выполняет очередь и вызвращает её результат (если таковой есть).</p>
<p>Но если надо выполнить очередь асинхронно - лучше использовать метод <code>Context.BeginInvoke</code>.<br/>
Он запускает асинхронное выполнение очереди и как только очередь была полностью запущена - возвращает объект типа <code>CLTask<></code>, у которого есть:</p>
<ul>
<li>Свойства, возвращающие оригинальную очередь и контекст выполнения.</li>
<li>Методы для ожидания окончания выполнения и получения результата очереди.</li>
</ul>
<p>Метод <code>Context.SyncInvoke</code> реализован как <code>.BeginInvoke(...).WaitRes</code>.
Поэтому, везде где сказано "... происходит при вызове <code>.BeginInvoke</code>", это же относится и к <code>.SyncInvoke</code>.</p>
<p>У<code>CLTask</code>, как и у очереди - в <code><></code>, указывается тип возвращаемого значения. То есть:</p>
<pre><code>var t: CLTask<integer>;
<p>Проверить что очередь ничего не возвращает очень просто:</p>
<pre><code>var Q: CommandQueueBase;
...
if Q is CommandQueueNil(var cqn) then
p1(cqn) else
p2(Q);
</code></pre>
<p>В такую переменную можно сохранить только результат <code>Context.BeginInvoke</code> для очереди типа <code>CommandQueue<integer></code>.</p>
<p>И как и у<code>CommandQueue</code>:</p>
<p>Но для типа <code>CommandQueue<T></code> надо указать конкретный тип, чтобы вызвать <code>is</code>.
Другими словами, с помощью <code>is</code> можно проверять только по 1 типу возвращаемого значения за раз:</p>
<pre><code>var Q: CommandQueueBase;
...
if Q is CommandQueueNil(var cqn) then
p1(cqn) else
if Q is CommandQueue<byte>(var cq) then
p2&<byte>(cq) else
if Q is CommandQueue<word>(var cq) then
p2&<word>(cq) else
// Не должно происходить
raise new System.NotSupportedException;
</code></pre>
<p>Если надо вызвать <code>p2</code> для очереди с любым возвращаемым значением - используется <code>.UseTyped</code>:</p>
<divid="page-21" page_name="Из объекта с коммандами"hidden=true>
<p>Самый просто способ создать очередь — выбрать объект, имеющий методы, соответствующие командам для GPU, и вызвав его метод <code>.NewQueue</code>.</p>
<p>Полученная очередь будет иметь особый тип, с припиской CCQ (что значит "Command Container Queue", то есть очередь-контейнер для коман GPU).<br/>
К примеру, метод <code>CLArray<byte>.NewQueue</code> вернёт очередь типа <code>CLArrayCCQ<byte></code>.<br/>
К примеру, метод <code>CLArray<byte>.NewQueue</code> вернёт очередь типа <code>CLArrayCCQ<byte></code>, наследующего от <code>CommandQueue< CLArray<byte>></code>.<br/>
К такой очереди можно добавлять команды, вызывая её методы, имена которых начинаются с<code>.Add</code>.</p>
<p>Команды объектов, представляющих память на GPU можно разделить на группы.</p>
<p>Команды объектов, представляющих память на GPU, можно разделить на группы.</p>
<p>По направлению передачи:</p>
<ol>
<li><code>Write</code> и <code>Fill</code>: Из RAM в память GPU;</li>
<li><code>Read</code>: Из памяти GPU в RAM;</li>
<li><code>Read</code> и <code>Get</code>: Из памяти GPU в RAM;</li>
<li><code>Copy</code>: Между двумя областями памяти GPU.</li>
<li><code>Get</code>: Как <code>Read</code>, но вместо использования существующей памяти RAM, выделяется новая.</li>
</ol>
<p>И по типу данных на стороне RAM:</p>
<ol>
<li><code>Data</code>: Используются данные, находящиеся по указанному адресу;</li>
<li><code>Value</code>: Используется копия указанного размерного значения;</li>
<li><code>Data</code>: Используются данные, находящиеся в RAM по указанному адресу;</li>
<li><code>Value</code>: Используется размерное значение;</li>
<li><code>Array</code>: Используется содержимое указанного массива размерных значений.</li>
</ol>
<p>Но при этом отсутствуют некоторые комбинации.</p>
<p>В первую очередь, в случае <code>Copy</code> нет понятия типа данных на стороне RAM, потому что RAM в принципе не задействуется.</p>
<p>Так же нет <code>ReadValue</code>, потому что нет смысла читать в копию указанного значения.<br/>
Вместо него надо использовать <code>GetValue</code> - создающее и возвращающее новое размерное значение.<br/>
Или <code>ReadData</code>, передавая адрес значения, которое надо перезаписать - но с этим надо осторожно, как и в случае <apath="../../Простые обёртки/Kernel/KernelArg"><code>KernelArg</code></a>.</p>
<p>И нет <code>GetData</code>, потому что в случае ошибок будут утечки памяти.
Вместо него выделяйте память явно и передавайте её в <code>ReadData</code>.</p>
<p>Так же, <code>WriteValue</code> может принимать размерное значение и <code>NativeValue</code>, но <code>ReadValue</code> принимает только <code>NativeValue</code>.<br/>
Это потому, что принимать размерное значение в <code>ReadValue</code><code>var</code>-параметром не безопастно,
как и в случае передачи адреса в качестве <apath="../../Простые обёртки/Kernel/KernelArg"><code>KernelArg</code></a>.<br/>
Если вы понимаете что делаете - используйте <code>ReadData</code>, явно передавая в него адрес вашего значения (то есть указатель).<br/>
Но обычно лучше использовать <code>GetValue</code>, создающее и возвращающее новое размерное значение, либо <code>ReadValue</code> принимающее <code>NativeValue</code>.</p>
<hr/>
<p>Кроме таких объектов, методы-команды для GPU есть только у<code>Kernel</code>. И все они представляют запуск kernel'а.</p>
<p>Они схожи с<code>HPQ</code> и <code>HFQ</code> соответственно, за исключением того, что <code>.ThenUse</code> возвращает значение, которое в него передали, а не <code>nil</code>.</p>
<p>Если надо с минимальными затратами производительности изменить представление компилятора об очереди - лучше всего использовать <code>.Cast</code>.<br/>
Но он ограничен примерно так же, как метод последовательностей <code>.Cast</code>. То есть:</p>
<pre><code>uses OpenCLABC;
@ -1674,7 +1797,7 @@ begin
// Можно, потому что к object можно преобразовать всё
<p>Большинство деревьев выполнения очередей можно реализовать используя только сложение и умножение очередей. Но есть несколько проблем:</p>
<ul>
<li><p>Большинство но не все. Пример дерева которое нельзя реализовать через сложение и умножение можно найти <ahref="https://github.com/SunSerega/POCGL/blob/master/Samples/OpenCLABC/Wait%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8/1.pas">тут</a> или в файле:<br/>
@ -1852,15 +1975,18 @@ t.Wait;
Внутри вызова <code>.BeginInvoke</code> (до того как он вернёт <code>CLTask</code>)
эта очередь входит в режим ожидания сигналов от маркера <code>M</code>.<br/>
В этом режиме она <strong>не</strong> тратит время процессора, но готова возобновить выполнение в любой момент.</p>
<p>Далее, вызов <code>M.SendSignal</code> посылает по 1 сигналу всем <code>.BeginInvoke</code>, имеющим <code>Wait</code>очереди в ожидании этого сигнала.</p>
<p>Далее, вызов <code>M.SendSignal</code> посылает по 1 сигналу всем <code>.BeginInvoke</code>, имеющим <code>Wait</code>-очереди в ожидании этого сигнала.</p>
<hr/>
<p><code>WaitMarker</code>, так же, является своеобразной очередью, то есть
его можно складывать с другими очередями и передавать в <code>.BeginInvoke</code> .<br/>
При выполнении в качестве очереди маркер вызывает свой метод <code>.SendSignal</code> и сразу возвращает <code>nil</code>, так же как <code>HPQ</code>.<br/>
То есть как очередь он равноценен <code>HPQ(M.SendSignal)</code>, но немного более эффективен.</p>
<p>Но стоит так же сказать, прямой вызов <code>M.SendSignal</code> всё равно всегда эффективнее чем <code>Context.Default.SyncInvoke(M)</code>.<br/>
<p><code>WaitMarker</code> не является очередью, но может быть преобразован к типу <code>CommandQueueBase</code>, в своеобразную выполняемую форму.</p>
<p>Преобразование в выполняемой форме обычно происходит автоматически, если складывать/умножать егос другими очередями,
либо передавать в подпрограмму принимающую <code>CommandQueueBase</code>, как <code>.BeginInvoke</code>.<br/>
Так же его можно вызвать явно, написав <code>CommandQueueBase(M)</code>, где <code>M</code> - маркер.</p>
<p>Выпоняемая форма маркера всегда вызывает метод <code>.SendSignal</code> своего макера.<br/>
Таким образом <code>CommandQueueBase(M)</code> обычно равноценна, но немного эффективнее чем <code>HPQ(M.SendSignal)</code>.<br/>
Но не всегда - потому что <code>HPQ</code> возвращает <code>nil</code>, а выполняемая форма маркера может возвращать другие результаты (об этом ниже).</p>
<p>И стоит так же сказать, прямой вызов <code>M.SendSignal</code> всё равно всегда эффективнее чем <code>Context.Default.SyncInvoke(M)</code>.<br/>
Используйте выполнение маркеров внутри <code>.BeginInvoke</code> только если вам надо активировать его сразу после других очередей:</p>
<pre><code>var M := new WaitMarker;
<pre><code>var M := WaitMarker.Create;
Context.Default.SyncInvoke(
HFQ(()->5) + M
@ -1868,7 +1994,7 @@ Context.Default.SyncInvoke(
</code></pre>
<hr/>
<p>Но в этом же коде видно ещё одну проблему - сложение очередей возвращает последний результат,
то есть результат маркера (который <code>nil</code>).</p>
то есть результат маркера (который для простого макера из <code>WaitMarker.Create</code> - <code>nil</code>).</p>
<p>Если нужно иметь сразу и маркер и возвращаемое значение предыдущей очереди,
можно создать оторванный сигнал маркера методом <code>.ThenMarkerSignal</code>:</p>
<pre><code>## uses OpenCLABC;
@ -1882,10 +2008,24 @@ var res := Context.Default.SyncInvoke( Q );
t.Wait;
res.Println;
</code></pre>
<p>Такой сигнал можно ожидать <code>Wait</code>-очередью, но при этом он
сохраняет результат оригинальной очереди при выполнении в <code>.BeginInvoke</code>.</p>
<p>Точнее <code>Q</code> в последнем коде сначала выполнит <code>HFQ</code>, затем пошлёт сигнал в
<code>Wait</code> очередь <code>WaitFor(Q)</code> и в конце вернёт то, что вернула <code>HFQ</code>.</p>
<p>Тут выполнение <code>Q</code> сначала выполнит <code>HFQ</code>, затем пошлёт сигнал в
<code>Wait</code>-очередь <code>WaitFor(Q)</code> и в конце вернёт то, что вернула <code>HFQ</code>.</p>
<p>В общем случае, оторванный сигнал макера является очередью, и имеет то же возвращаемое значение, что очередь из которой он был создан.<br/>
Но в то же время его можно преобразовать типу <code>WaitMarker</code>, так же как <code>WaitMarker</code> преобразовывается в <code>CommandQueueBase</code>.<br/>
Более того, выполняемая форма полученного маркера - это оторванный сигнал, из которого создали этот маркер.
Другими словами даже после преобразования в <code>WaitMarker</code> - оторванный сигнал можно выполнить и получить его результат:</p>
<pre><code>## uses OpenCLABC;
var Q := HFQ(()->5).ThenMarkerSignal;
var M: WaitMarker := Q;
Writeln(Context.Default.SyncInvoke(
CommandQueueBase(M)
// CommandQueueBase надо всё равно как то преобразовать,
// чтобы SyncInvoke вернуло результат Q
.Cast&<integer>
));
</code></pre>
<hr/>
<p>Того же эффекта можно добится используя <code>.Multiusable</code>:</p>
<pre><code>## uses OpenCLABC;
@ -1902,7 +2042,7 @@ res.Println;
</code></pre>
<p>То есть <code>.ThenMarkerSignal</code> не имеет незаменимых применений, но делает код читабельнее.</p>
<hr/>
<p>Есть всего 3 подпрограммы, создающие <code>Wait</code>очереди:</p>
<p>Есть всего три подпрограммы, создающие <code>Wait</code>-очереди:</p>
<ol>
<li><p>Глобальная, <code>WaitFor</code>:<br/>
Ничего не делает сама, но блокирует выполнение, ожидая сигнала указанного маркера.</p>
@ -1936,13 +2076,13 @@ WaitFor(WaitAll(M1,M2,M3)); // Ожидание всех маркеров
WaitFor(WaitAny(M1,M2,M3)); // Ожидание любого из маркеров
</code></pre>
<hr/>
<p><code>Wait</code>очереди работают даже между вызовами <code>Context.BeginInvoke</code>, в отличии от всего остального в <code>OpenCLABC</code>.</p>
<p><code>Wait</code>-очереди работают даже между вызовами <code>Context.BeginInvoke</code>, в отличии от всего остального в <code>OpenCLABC</code>.</p>
<p>Это не всегда безопастно:</p>
<pre><code>Context.Default.BeginInvoke(M);
Context.Default.BeginInvoke(WaitFor(M) + Q);
</code></pre>
<p>Проблема этого кода в том, что <code>M</code> может послать сигнал ещё до того как <code>WaitFor(M)</code> начнёт ждать.</p>
<p>Чтоб такое не происходило - надо всегда запускать <code>Wait</code>очередь раньше маркера:</p>
<p>Чтобы такое не происходило - надо всегда запускать <code>Wait</code>-очередь раньше маркера:</p>
<p>Все<code>Wait</code>очереди начинают ждать в самом начале вызова <code>Context.BeginInvoke</code>, перед началом выполнения очереди.
Поэтому если <code>Wait</code>очередь и вызов её маркера находятся в общем <code>Context.BeginInvoke</code> - использовать их можно в любом порядке.</p>
<p>Все<code>Wait</code>-очереди начинают ждать в самом начале вызова <code>Context.BeginInvoke</code>, перед началом выполнения очереди.
Поэтому если <code>Wait</code>-очередь и вызов её маркера находятся в общем <code>Context.BeginInvoke</code> - использовать их можно в любом порядке.</p>
<hr/>
<p>Все<code>Wait</code>очереди в одном <code>Context.BeginInvoke</code>, ожидающие один и тот же маркер, образуют общую <code>Wait</code>группу.<br/>
Когда ожидаемый этой группой маркер активируется - он удаляет из <code>Wait</code>группы одну из <code>Wait</code>очередей, посылая ей сигнал.</p>
<p>Все<code>Wait</code>-очереди в одном <code>Context.BeginInvoke</code>, ожидающие один и тот же маркер, образуют общую <code>Wait</code>-группу.<br/>
Когда ожидаемый этой группой маркер активируется - он удаляет из <code>Wait</code>-группы одну из <code>Wait</code>-очередей, посылая ей сигнал.</p>
<p><code>Wait</code>очереди ожидающие один и тот же маркер в разных <code>Context.BeginInvoke</code> - образуют отдельные <code>Wait</code>группы.<br/>
И при активации маркера - он посылает по 1 сигналу <strong>каждой</strong><code>Wait</code>группе:</p>
<p><code>Wait</code>-очереди ожидающие один и тот же маркер в разных <code>Context.BeginInvoke</code> - образуют отдельные <code>Wait</code>-группы.<br/>
И при активации маркера - он посылает по 1 сигналу <strong>каждой</strong><code>Wait</code>-группе:</p>
<p>Кроме самого выполнения очередей - им так же необходима инициализация и финализация.</p>
<hr/>
<h3>Инициализация</h3>
<p>Для начала, перед тем как любая из под-очередей в <code>BeginInvoke</code> начнёт выполняться - необходимо инициалировать <code>Wait</code> очереди.
Иначе у ожидаемой очереди всегда будет шанс выполнится до того как ожидающая начнёт ожидать.</p>
Иначе у ожидаемого маркера всегда будет шанс выполнится до того как ожидающаяего очередь начнёт ожидать.</p>
<p>Инициализация <code>Wait</code> очередей заключается в обходе всего дерева под-очередей.
Для каждой группы <code>Wait</code> очередей, ожидающих общий маркер в общем <code>.BeginInvoke</code>,