Задать вопрос
  • Как реализованы асинхронные методы c#?

    @mvv-rus
    Настоящий админ AD и ненастоящий программист
    Вот вам перевод (часть первая, а всего там шесть частей) фундаменнтальной статьи по async/await одного из разрабочиков .NET в Microsoft (а именно - Stephen Toub).
    Читайте и просвещайтесь.

    PS Автор вопроса никак не желает считать текст выше ответом на свой вопрос - а зря. Потому что по-другому на форуме на него не ответишь - слишком много букв писать надо. Но сегодня - выходной, так что - попробую.
    И самое главное, что вам следует осознать - что код асинхронного метода, который вы написали, и код машины состояний, в которой он преобразуется (и далее - транслируется в IL и компилируется в команды процессора) - это два разных кода, пусть и эквивалентных.
    Если очень вкратце:
    • компилятор преобразует код асинхронного метода до неузнаваемости, в ту самую "машину состояний": класс, в котором есть метод, ее реализующий, и есть поля для данных, как нужных для работы самой машины состояний (в частности - для хранения состояния), так и для хранения локальных переменных исходного метода (да-да, в стеке они не хранятся);
    • каждый кусок исходного метода - начальный, между двумя await и завершающий - преобразуется в отдельный кусок метода машины состояний, выполняемый, когда выполнение машины состояний возобновляется в определенном состоянии; этот код производит действия, определенные в этом куске метода, и либо меняет состояние на новое значение и переходит к ожиданию (там, где в исходном асинхронном методе написан оператор await), либо завершает выполнение метода машины состояний;
    • для выполнения перехода к ожиданию используется объект awaiter'а, получаемый методом GetAwaiter() операнда операции await;
    • перед переходом к ожиданию проверяется возможно продолжение без ожидания, если это возможно (метод IsCompleted() awaiter'а возвращает true), то ожидания не происходит, а возобновляется выполнение машины состояний уже для нового состояния;
    • запуск ожидания происходит во внешнем относительно асинхронного метода коде - в объекте awaiter'а;
    • и при завершении метода машины состояний, и при запуске ожидания метод машины состояний возвращает управление в код, который вызвал в этот раз метод машины состояний (для одного вызова асинхронного метода вызовов метода машины состояний может быть много, см. дальше);
    • первый возврат из метода машины состояний происходит в вызывающий метод, он возвращает объект задачи, позволяющий отследить в вызывающем методе завершение работы машины состояний ( и тут есть нюанс, связанный с возвращением ValueTask, а не Task);
    • при окончательном (а не при запусе ожидания) выходе из метода машины состояний этот возвращенный объект задачи завершается;
    • после завершения ожидания метод машины состояний вновь вызывается: в типичном случае - в свободном потоке из пула потоков, но возможны варианты (какие - курить тему Synchronization Context); и он продолжает свое выполнение для нового состояния

    Это - максимально урезанное описание работы асинхронного метода.
    Если вы смогли из этого описания понять ответ на ваш первый вопрос - хорошо. Если нет - читайте статьи(ссылка выше), или книги (лично я рекомендую главу про асинхронные меотоды из книги Рихтера "CLR via C#").
    Касательно второго вопроса. Задачи, выполнение которых ожидает асинхронный метод, будут выполняться совершенно параллельно, каждая - в своем потоке из пула, возможно - на разных ядрах/процессорах). А асинхронный метод в это время не будет выполняться совсем: поток в котором он выполнялся, будет дальше выполнять код после его вызова в вызвавшем его методе (или код, возвращий поток в пул, если это был вызов машины состояний после ожидания). А после завершения обеих этих задач выполнение метода машины состояний, в который был преобразован асинхроннный метод, будет возобновлено и задача, которую возвратил асинхронный метод, будет завершена.
    Ответ написан
    Комментировать
  • Как правильно смоделировать систему N колизий тел, прям абсолютно точно?

    @rPman
    Вместо итеративного алгоритма, можно подойти к расчету до пересечения кого либо, т.е. каждый объект имеет вектор движения и его коллайдер (в случае с простыми геометрическими фигурами типа сфера или квадрат с параллельными осям поверхностями это простое уравнение, в крайнем случае можно свести все к треугольникам или если 3d то тетраэдрам и строить объекты уже из них, ну а для оптимизации, не считать самостолкновения), получив точку, считаешь касательную к колайдеру и считаешь силы и правишь вектора скорости, а там уже по ситуации (нужно ли добавлять вращение, а то тогда уравнение пересечения будет сильно сложнее), тут же можно учитывать упругость столкновения.

    Так как для большого количества окружностей считать уравнение будет экспонента (квадрат от количества) то можно уже вводить оптимизации сортируя по суммарной скорости между объектами (список вести для каждого), т.е. нет нужды искать пересечение между объектами, летящих друг от друга или слишком медленно

    С другой стороны, после столкновения, пересчитывать проверку пересечения придется только для этих двух столкнувшихся объектов, т.е. трудоемкость линейная

    upd. если подумать, оптимизация не такая простая, но грубые оценки можно делать учитывая тоносительную скорость между текущим объектом и исследуемым, а так же его расстояние, т.е. чем быстрее и чем ближе объект находится тем выше в списке на проверку он должен быть (т.е. разница скоростей между текущим и исследуемым/расстояние между ними), к сожалению считать правильную скорость сближения 'дорого', нужно подумать, не получится ли хуже при такой оценке...

    и да, искать пересечение прямоугольников сильно проще чем окружностей или более сложных объектов, т.е. для оптимизации сначала ищем для них (в которые вписаны) а затем уже для тех кто пересекся, считать полностью

    ну и конечно классическое разбиение на ячейки, размер подбирать под предельные и средние значения скоростей, можно делить объекты на группы - быстрые и медленные, считать сначала быстрые, в ячейках на его пути и т.п.
    Ответ написан
    Комментировать
  • Как правильно смоделировать систему N колизий тел, прям абсолютно точно?

    hint000
    @hint000
    у админа три руки
    прям идеальная. с 100 нюансов

    На что влияет глубина проникновения объектов коллизий, как ее обрабатывать, создает ли она дополнительный какой-то импульс, меньший или больший,.
    Например при врезании шара в бокс, шар проник на расстояние h.

    Если прям идеально, то не влияет глубина ни на что. Потому что эта глубина - просто результат конечного шага по времени модели. Идеально - вам после проникновения нужно сделать шаг назад во времени до точки прикосновения. Для этого нужно интерполировать из траекторий и скоростей объектов момент времени, когда они соприкоснулись без проникновения. И откатить все вычисления ровно до этого момента (в пределах погрешности интерполяции, конечно; мизерный "недолёт" или "перелёт" просто игнорируем, как будто его нет и как будто имеем точное прикосновение). Таким образом, шаг по времени должен быть изменяемый, а не по фиксированной сетке. Именно такая модель обеспечит правильное условие независимости скоростей, импульсов и т.п. от сетки времени. Но, возможно, вы и так уже это реализовали, поскольку далее встречается слово "раздвинуть".
    коллизия случилась, следовательно надо раздвинуть, теперь представим, вот мы раздвигаем 2 объекта, и 2-ой объект может проникнуть в 3-ий объект рядом
    А тут нужно после шага назад, иниццированного 1-м и 2-м объектами, проверять их оба на пересечение с 3-м, и если пересечение (проникновение) есть, то дополнительно ещё откатывать назад во времени, и шаг отката опять нужно интерполировать, а не брать фиксированный.
    Согласен, когда одновременно очень близко 100 объектов (да даже 10), то довольно сложно будет определить, кто из них с кем столкнётся раньше, проблема на самом деле есть (по центрам масс легко определить очерёдность, а вот зацепы по касательной - неприятная штука).

    Для смягчения проблемы можно сделать вот что. Берёте характерные размеры R[i] соседних объектов (условный радиус можно определить и для прямоугольника, этот размер нам потребуется для оценки, а не для точных рассчётов). Берёте текущие расстояния S[i,j] между ними. И по мере их сближения уменьшаете шаг времени обратно произведению логарифмов R[i]*R[j]/S[i,j]. Таким образом вы будете как бы рассматривать замедленную съёмку "взрыва" (массового столкновения), где можно будет точнее поймать факт, что 3-й успел столкнуться со 2-м ещё до того, как столкнулись 1-й и 2-й. Т.е. вы, сравнивая размеры объектов и расстояния между ними, заранее прогнозируете столкновение и готовитесь рассчитать его точнее, уменьшая шаг времени. Это позволит меньше откатывать время назад и меньше путаться в куче взаимодействующих объектов.
    Ответ написан
    2 комментария
  • Как реализовать потоковое заполнение массива другим потоком?

    @mvv-rus
    Настоящий админ AD и ненастоящий программист
    Сколько там недочетов. потенциальных ошибок. неверных решений?

    1. У вас намешаны синхронная - в с одном потоке с блокировками (Wait, Sleep) - и асинхронная (async/await) многозадачность Теоретически, совмещать их можно, но лучше остановиться на каком-то одном подходе - легче будет. Синхронное выполнение задач (каждая - в своем потоке, который она блокирует по необходимости) - это проще, но асинхронное выполнение (при которм поток не блокируется, а освобождается, а задача планируется для продолжения после завершения операции в свободном потоке) позволяет получить больше производительности. Вот и выбирайте, что вам нужнее.
    2. Для синхронизации используйте не переменные (а, к примеру, isRun у вас используется именно для этого), а объекты синхронизации. В частности, isRun следует заменить на объект синхронизации. Для синхронного выполнения лучше всего IMHO подойдет ManualResetEventSlim. для асинхронного - что-нибудь, на чем можно сделать await (например, TaskCompletionSource.Task).
    3. Любой доступ к совместно используемым в разных задачах объектам(ресурсам) следует синхронизировать путем захвата связанного с ресурсом объекта исключающего доступа. Для синхронного выполнения подойдет оператор lock (вижу, что вы его уже используете). Для асихронного выполнения lock можно использовать, но - только если в его теле нет await (и компилятор вам не позволит нарушить это правило). Не пытайтесь обойти это правило, используя вместо lock другие объекты синхронизации, подразумевающие захват владения на уровне потока (Monitor, Mutex...) - компилятор всего лишь не сможет вам помешать сделать ошибку. В таком случае для синхронизации можно использовать SemaphoreSlim, но это требует понимания и аккуратности, поэтому подробности пока не пишу.
    Думаю, пока этой информации к размышлению вам хватит.
    Ответ написан
    1 комментарий