В частности, в Unity «куда» буквально возвращается ожидание?

2

В Unity, скажем, у вас есть GameObject. Таким образом, это может быть Лара Крофт, Марио, злая птица, определенный куб, определенное дерево или что-то еще.

Изображение 174551

(Напомним, что Unity - это не OO, это ECS. Сами Component которые вы можете "прикрепить" к GameObject могут создаваться или не создаваться на языке OO, но сам Unity - это просто список GameObject и движок фреймов, который запускает любой Component на каждом кадре. Таким образом, действительно, Unity, конечно, "совершенно" однопоточный, даже нет концептуального способа сделать что-либо, связанное с "фактическим Unity" ("списком игровых объектов") в другом 1 потоке.)

Так, скажем, на кубе у нас есть Component под названием Test

public class Test: MonoBehaviour {

У него есть псевдофункция Update, поэтому Unity знает, что мы хотим запускать что-то каждый кадр.

  private void Update() { // this is Test Update call

     Debug.Log(ManagedThreadId); // definitely 101
     if (something) DoSomethingThisParticularFrame();
  }

Допустим, единство потока "101".

Таким образом, это Обновление (и действительно любое Обновление любого фрейма на любом игровом объекте) напечатает 101.

Поэтому время от времени, возможно, каждые несколько секунд по какой-то причине мы выбираем запуск DoSomethingThisFrame.

Таким образом, каждый кадр (очевидно, в "потоке Unity... есть/может быть только один поток) Unity выполняет все вызовы Update для различных игровых объектов.

Итак, на одном конкретном кадре (скажем, 24-м кадре из 819-й секунды игрового процесса), допустим, он запускает DoSomethingThisParticularFrame для нас.

void DoSomethingThisParticularFrame() {

   Debug.Log(ManagedThreadId); // 101 I think
   TrickyBusiness();
}

Я предполагаю, что это также напечатает 101.

async void TrickyBusiness() {

   Debug.Log("A.. " + ManagedThreadId); // 101 I think
   var aTask = Task.Run(()=>BigCalculation());

   Debug.Log("B.. " + ManagedThreadId); // 101 I think
   await aTask;

   Debug.Log("C.. " + ManagedThreadId); // In Unity a mystery??
   ExplodeTank();
}

void BigCalculation() {

   Debug.Log("X.. " + ManagedThreadId); // say, 999
   for (i = 1 to a billion) add
}

Итак

  1. Я уверен, что в А это напечатает 101. Я думаю.

  2. Я думаю, что в B это будет печатать 101

  3. Я верю, но я не уверен, что в X он запустит другой поток для BigCalculation. (Скажите, 999.) (Но, может быть, это не так, кто знает.)

Мой вопрос, что происходит в Unity на "C"?

Какой поток мы на C, где он (пытается?) Взорвать танк????

Я верю, что в нормальных средах .Net вы будете в другом потоке на C, скажем, 202.

(Например, рассмотрите этот превосходный ответ и обратите внимание на первый пример вывода "Thread After Await: 12". 12 отличается от 29.)

Но это бессмысленно в Unity -

... как TrickyBusiness может быть в "другом потоке" - что бы это значило, что вся сцена дублирована, или?

Или это так (в Unity особенно и только? IDK),

в момент, когда начинается TrickyBusiness, Unity фактически помещает это (что - голый экземпляр класса "Test"??) в другой поток?

В Unity, когда вы используете await что печатается на C или A?

Казалось бы, что:

Если действительно "C" находится в другом потоке - вы просто не можете использовать ожидание таким образом в Unity, это было бы бессмысленно.


1 Очевидно, что некоторые вспомогательные вычисления (например, рендеринг и т.д.) Выполняются на других ядрах, но фактический "игровой движок на основе фреймов" является одним чистым потоком. (Невозможно каким-либо образом "получить доступ" к потоку фрейма основного движка: когда вы программируете, скажем, собственный плагин или какой-то расчет, выполняющийся в другом потоке, все, что вы можете сделать, это оставить маркеры и значения для компонентов на нить рамы двигателя, чтобы посмотреть и использовать, когда они запускают каждый кадр.)

  • 1
    Нет. C было бы 101 .. aTask было бы, скажем, 102. но там, где вы печатаете C, все равно 101, так что в вашем примере выше A, B и C все печатают 101.
  • 0
    @BugFinder - спасибо - одна вещь, если вы нажмете на этот подробный пример, stackoverflow.com/a/55604269/294884, писатель скажет обратное: обратите внимание на первый пример, вывод «29/29/12» ..... ......... ???
Показать ещё 13 комментариев
Теги:
unity3d
multithreading

1 ответ

1

Async как абстракция высокого уровня не имеет отношения к потокам.

В каком потоке выполнение возобновляется после того, как await контролируется System.Threading.SynchronizationContext.Current.

Например, WindowsFormsSynchronizationContext обеспечит возобновление выполнения, запущенного в потоке графического интерфейса, в потоке графического интерфейса после await, поэтому, если вы выполните тест в приложении WinForms, вы увидите, что ManagedThreadId остается таким же после await.

Например, AspNetSynchronizationContext не заботится о сохранении потоков и позволит возобновить код в любом потоке.

Например, ASP.NET Core вообще не имеет контекста синхронизации.

Что бы ни случилось в Unity, зависит от того, что он имеет в качестве SynchronizationContext.Current. Вы можете проверить, что он возвращает.


Выше приведено "достаточно верное" представление событий, то есть того, что вы можете ожидать от своего обычного скучного повседневного асинхронного/ожидающего кода, связанного с обычными функциями Task<T> которые возвращают свои результаты обычным способом.

Вы абсолютно можете настроить эти поведения:

  • Вы можете отказаться от захвата контекста , вызвав ConfigureAwait(false) со своими ожиданиями. Поскольку контекст не фиксируется, все, что идет с контекстом, теряется, включая возможность возобновления в исходном потоке (для контекстов, связанных с потоками).

  • Вы можете разработать асинхронный код, который намеренно переключает вас между потоками, даже если вы не используете ConfigureAwait(false). Хороший пример можно найти в блоге Рэймонда Чена (часть 1, часть 2) и показывает, как явно перейти на другой поток в середине метода с помощью

    await ThreadSwitcher.ResumeBackgroundAsync();
    

    а затем вернуться с

    await ThreadSwitcher.ResumeForegroundAsync(Dispatcher);
    
  • Поскольку весь механизм async/await слабо связан (вы можете await любой объект, который определяет метод GetAwaiter()), вы можете создать объект, для которого GetAwaiter() делает все, что вам нужно, с текущим потоком/контекстом (фактически, это это именно то, что вышеупомянутый пункт пули).

SynchronizationContext.Current волшебным образом не навязывает свой код другим людям: он работает наоборот. SynchronizationContext.Current действует только потому, что реализация Task<T> выбирает его соблюдение. Вы можете реализовать другое ожидание, которое игнорирует его.

  • 0
    Это должен быть один из лучших ответов на всем сайте. «В каком потоке выполнение возобновляется после того, как ожидание контролируется System.Threading.SynchronizationContext.Current.» Я удивлен , я не нашел / попадались эту информацию в любом месте, несмотря на то задавать много подобных вопросов, и т.д. Ура!
  • 2
    Что касается возобновления выполнения, я бы сказал, что оно «не на 100% точно, поскольку ConfigureAwait также является фактором, влияющим на это. Кроме того, async/await - это просто абстракция, и вы можете создать свой собственный awaiter / awaitable, который будет всегда возобновляться в исходном потоке или просто игнорировать контекст синхронизации вообще.
Показать ещё 4 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню