В Unity3D, как обрабатывать события в правильном потоке
Я пишу сценарий Unity3D и использую сетевую библиотеку. Библиотека выдает события (вызывает делегатов), когда данные готовы. Моя библиотека считывает эти данные и выдает события, которые пытаются получить доступ GameObject
но я получаю эти ошибки
CompareBaseObjectsInternal can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread
when loading a scene. Don't use this function in the constructor or field
initializers, instead move initialization code to the Awake or Start function.
ArgumentException: CompareBaseObjectsInternal can only be called from the main
thread. Constructors and field initializers will be executed from the loading
thread when loading a scene. Don't use this function in the constructor or field
initializers, instead move initialization code to the Awake or Start function.
это нормально. Я думаю, что понимаю, что сетевые сообщения обрабатываются в отдельном потоке, и Unity3D пытается указать на это, и как небезопасно манипулировать вещами из другого потока.
мой вопрос: каков рекомендуемый способ справиться с этим?
Я думаю, что вместо вызова делегата непосредственно в моем обработчике я сделаю какой-то объект, который ссылается на все параметры, поставляемые в событии, и добавляет их в List<MyEvent>
событий.
мне нужно сделать класс thread safe wrapper, чтобы содержать этот список, который позволяет мне добавлять и удалять события с блокировкой, потому что List<>
не является потокобезопасным.
мне тогда как-то нужно обработайте эти события в главном потоке. Я не уверен, что это лучший способ сделать. Класс, который имеет дело с сетью, является MonoBehavior
сам поэтому я думаю, что могу иметь его Update
метод удалить события из списка и отправить их.
это звучит как хорошая идея или есть какой-то другой, лучший или более простой способ достичь этого?
4 ответов
вот что я закончил.
public class EventProcessor : MonoBehaviour {
public void QueueEvent(Action action) {
lock(m_queueLock) {
m_queuedEvents.Add(action);
}
}
void Update() {
MoveQueuedEventsToExecuting();
while (m_executingEvents.Count > 0) {
Action e = m_executingEvents[0];
m_executingEvents.RemoveAt(0);
e();
}
}
private void MoveQueuedEventsToExecuting() {
lock(m_queueLock) {
while (m_queuedEvents.Count > 0) {
Action e = m_queuedEvents[0];
m_executingEvents.Add(e);
m_queuedEvents.RemoveAt(0);
}
}
}
private System.Object m_queueLock = new System.Object();
private List<Action> m_queuedEvents = new List<Action>();
private List<Action> m_executingEvents = new List<Action>();
}
код запуска в каком-то другом классе добавляет один из них в GameObject, который его запустил. Сетевой поток добавляет действие, вызывая eventProcessor.queueEvent
и Update
функция выше завершает выполнение этих действий в основном потоке. Я вытягиваю все действия в другую очередь, чтобы держать вещи запертыми как можно меньше.
Я думаю, это не так сложно. Мне просто интересно, есть ли другие рекомендации решение.
Я пока не могу комментировать, поэтому добавляю ответ.
ваша интуиция верна.
вы не класс, который обрабатывает замок, достаточно просто использовать блокировку при добавлении в список.
использование метода обновления является рекомендуемым способом, так как он всегда будет срабатывать в потоке пользовательского интерфейса, поэтому любое событие, которое запускается, может изменить список отображения.
Если вы хотите, вы можете разделить задачу получения данных из задачи запуска событий наличие отдельного MonoBehaviour, который просто обрабатывает диспетчеризацию событий.
Я создал простой класс по этой причине, что вы можете вызвать с одной строкой.
вы можете использовать его как это:
public IEnumerator ThisWillBeExecutedOnTheMainThread() {
Debug.Log ("This is executed from the main thread");
yield return null;
}
public void ExampleMainThreadCall() {
UnityMainThreadDispatcher.Instance().Enqueue(ThisWillBeExecutedOnTheMainThread());
}
просто перейдите к https://github.com/PimDeWitte/UnityMainThreadDispatcher и начните использовать его, если хотите.
Ура
вы можете создавать потоки, однако ничего не делайте с GameObject или любыми компонентами GameObjects в отдельном потоке.
но я действительно не понимаю, почему вам нужно использовать его, почему вы не можете просто использовать Coroutines?
void SomeMethod()
{
StartCoroutine(CallNetworkCode());
// ...keep going...
}
IEnumerator CallNetworkCode()
{
bool done = false;
var request = LibraryNetwork.DoSomeRequest(() => { done = true; });
while (!done)
{
yield return 0; // wait for next frame
}
if (request.result != null)
Debug.Log("ha!");
}
или просто назначьте делегату код разрешения результата.