Unity에서 메인 스레드를 고정하지 않는 방법은 무엇입니까?


33

계산적으로 무거운 레벨 생성 알고리즘이 있습니다. 따라서 호출하면 항상 게임 화면이 정지됩니다. 게임이 계속 멈추는 동안 게임이 계속 로딩 화면을 렌더링하는 동안 두 번째 스레드에 함수를 배치하려면 어떻게해야합니까?


1
스레딩 시스템을 사용하여 백그라운드에서 다른 작업을 실행할 수 있지만 Unity API에서는 스레드 안전이 아니기 때문에 아무것도 호출하지 않을 수 있습니다. 이 작업을 수행하지 않았으므로 자신있게 예제 코드를 신속하게 제공 할 수 없기 때문에 댓글을 달았습니다.
Almo

List메인 스레드에서 호출하려는 함수를 저장하기 위해 액션 사용 . 함수 의 Action목록 Update을 임시 목록에 잠그고 복사하고 원래 목록을 지우고 주 스레드 Action에서 해당 목록의 코드 를 실행하십시오 List. 이 작업을 수행하는 방법에 대해서는 다른 게시물의 UnityThread를 참조하십시오 . 예를 들어, 메인 스레드에서 함수를 호출하려면 UnityThread.executeInUpdate(() => { transform.Rotate(new Vector3(0f, 90f, 0f)); });
Programmer

답변:


48

업데이트 : 2018 년 Unity는 작업 을 오프로드하고 여러 CPU 코어를 사용하는 방법으로 C # 작업 시스템 을 출시하고 있습니다.

아래의 답변은이 시스템보다 우선합니다. 여전히 작동하지만 필요에 따라 최신 Unity에서 더 나은 옵션을 사용할 수 있습니다. 특히, 작업 시스템은 아래에 설명 된 수동으로 작성된 스레드가 안전하게 액세스 할 수있는 것에 대한 일부 제한 사항을 해결하는 것으로 보입니다. 예를 들어, 레이 캐스트를 수행하고 메시를 병렬로 구성하는 미리보기 보고서를 실험하는 개발자 .

이 작업 시스템을 사용한 경험이있는 사용자를 초대하여 엔진의 현재 상태를 반영한 ​​고유 한 답변을 추가합니다.


과거에는 Unity에서 무거운 작업에 스레딩을 사용했지만 (일반적으로 이미지 및 지오메트리 처리) 다른 C # 응용 프로그램에서 스레드를 사용하는 것과 크게 다르지 않습니다.

  1. Unity는 다소 오래된 .NET 하위 집합을 사용하기 때문에 사용할 수없는 새로운 스레딩 기능과 라이브러리가 있지만 기본 사항이 모두 있습니다.

  2. Almo가 위의 주석에서 언급했듯이 많은 Unity 유형은 스레드 안전하지 않으며 주 스레드에서 구성, 사용 또는 비교하려고하면 예외가 발생합니다. 명심해야 할 사항 :

    • 한 가지 일반적인 경우는 멤버에 액세스하기 전에 GameObject 또는 Monobehaviour 참조가 null인지 확인하는 것입니다. myUnityObject == nullUnityEngine.Object의 하위 항목에 대해 오버로드 된 연산자를 호출하지만 System.Object.ReferenceEquals()이 문제를 어느 정도 해결합니다. Destroy () ed GameObject는 오버로드를 사용하여 null과 같지만 아직 ReferenceEqual이 아니라는 점을 기억하십시오.

    • Unity 타입에서 파라미터를 읽는 것은 일반적으로 다른 스레드에서 안전합니다 (위와 같이 null을 확인하는 한 조심스럽게 예외를 throw하지는 않습니다). 그러나 여기 에서 메인 스레드가 상태를 수정하고 있다는 필립의 경고에 주목하십시오. 당신이 그것을 읽는 동안. 일관성이없는 상태를 읽는 것을 피하기 위해 누가 무엇을 언제 수정해야하는지에 대해 훈련을 받아야합니다. 이로 인해 버그가 발생할 수 있습니다. 마음대로 재생산하지 않습니다.

    • 임의 및 시간 정적 멤버는 사용할 수 없습니다. 임의성이 필요한 경우 스레드 당 System.Random 인스턴스를 작성하고 타이밍 정보가 필요한 경우 System.Diagnostics.Stopwatch 인스턴스를 작성하십시오.

    • Mathf 함수, Vector, Matrix, Quaternion 및 Color 구조체는 스레드에서 모두 잘 작동하므로 대부분의 계산을 개별적으로 수행 할 수 있습니다.

    • 메인 오브젝트에서 게임 오브젝트 생성, 모노 동작 부착, 텍스쳐, 메시, 머티리얼 생성 / 업데이트가 모두 필요합니다. 과거에 이러한 작업을 수행해야 할 때 작업자 스레드가 원시 데이터를 준비하는 생산자-소비자 대기열을 설정했습니다 (메시 또는 텍스처에 적용 할 벡터 / 컬러 배열). 메인 스레드의 업데이트 또는 코 루틴은 데이터를 폴링하여 적용합니다.

그 노트를 벗어난 상태에서, 내가 스레드 작업에 자주 사용하는 패턴이 있습니다. 나는 그것이 최선의 연습 스타일이라는 것을 보장하지는 않지만 작업을 완료합니다. (개선을위한 의견이나 편집은 환영합니다-스레딩은 기본 사항 만 알고있는 매우 깊은 주제라는 것을 알고 있습니다)

using UnityEngine;
using System.Threading; 

public class MyThreadedBehaviour : MonoBehaviour
{

    bool _threadRunning;
    Thread _thread;

    void Start()
    {
        // Begin our heavy work on a new thread.
        _thread = new Thread(ThreadedWork);
        _thread.Start();
    }


    void ThreadedWork()
    {
        _threadRunning = true;
        bool workDone = false;

        // This pattern lets us interrupt the work at a safe point if neeeded.
        while(_threadRunning && !workDone)
        {
            // Do Work...
        }
        _threadRunning = false;
    }

    void OnDisable()
    {
        // If the thread is still running, we should shut it down,
        // otherwise it can prevent the game from exiting correctly.
        if(_threadRunning)
        {
            // This forces the while loop in the ThreadedWork function to abort.
            _threadRunning = false;

            // This waits until the thread exits,
            // ensuring any cleanup we do after this is safe. 
            _thread.Join();
        }

        // Thread is guaranteed no longer running. Do other cleanup tasks.
    }
}

작업 속도를 높이기 위해 스레드간에 작업을 엄격하게 분할 할 필요가없고 비 차단 방식으로 게임의 나머지 부분이 계속 똑딱 거리는 방법을 찾고 있다면 Unity의 더 가벼운 솔루션은 Coroutines 입니다. 이러한 기능은 일부 작업을 수행 한 다음 엔진으로 다시 제어하여 작업을 계속하고 나중에 원활하게 재개 할 수있는 기능입니다.

using UnityEngine;
using System.Collections;

public class MyYieldingBehaviour : MonoBehaviour
{ 

    void Start()
    {
        // Begin our heavy work in a coroutine.
        StartCoroutine(YieldingWork());
    }    

    IEnumerator YieldingWork()
    {
        bool workDone = false;

        while(!workDone)
        {
            // Let the engine run for a frame.
            yield return null;

            // Do Work...
        }
    }
}

엔진은 (내가 알 수있는 한) 파괴 된 물체에서 코 루틴을 제거하기 때문에 특별한 정리 고려 사항이 필요하지 않습니다.

메소드의 모든 로컬 상태는 생성되고 재개 될 때 유지되므로 많은 목적을 위해 다른 스레드에서 중단없이 실행되는 것처럼 보입니다 (그러나 주 스레드에서 실행하는 모든 편의는 제공됩니다). 메인 스레드가 부당하게 느려지지 않을 정도로 짧을 때마다 확인해야합니다.

중요한 작업이 수율로 분리되지 않도록하여 단일 스레드 동작의 일관성을 얻을 수 있습니다. 기본 스레드의 다른 스크립트 나 시스템이 작업중인 데이터를 수정할 수 없다는 것을 알고 있습니다.

수익률 반환 라인은 몇 가지 옵션을 제공합니다. 당신은 할 수 ...

  • yield return null 다음 프레임의 Update () 후에 다시 시작
  • yield return new WaitForFixedUpdate() 다음 FixedUpdate () 후에 다시 시작
  • yield return new WaitForSeconds(delay) 일정 시간이 지나면 다시 시작
  • yield return new WaitForEndOfFrame() GUI 렌더링 완료 후 재개
  • yield return myRequest여기서 myRequestA는 WWW의 인스턴스가 요청 된 데이터가 웹 또는 디스크에서로드가 완료되면 다시 시작합니다.
  • yield return otherCoroutine완료 후 재개 otherCoroutineCoroutine 인스턴스 는 어디에 있습니까 otherCoroutine? 이것은 종종 yield return StartCoroutine(OtherCoroutineMethod())원하는 때에 생성 할 수있는 새로운 코 루틴에 실행을 연결 하는 형태 로 사용 됩니다.

    • 실험적으로, 두 번째를 건너 뛰고 StartCoroutine단순히 쓰는 yield return OtherCoroutineMethod()것은 동일한 맥락에서 실행을 연결하려는 경우 동일한 목표를 달성합니다.

      StartCoroutine중첩 된 코 루틴을 두 번째 객체와 관련하여 실행하려는 경우에도 내부를 감싸는 것이 여전히 유용 할 수 있습니다yield return otherObject.StartCoroutine(OtherObjectsCoroutineMethod())

코 루틴이 다음 차례를 원할 때에 따라

또는 yield break;코 루틴이 끝날 때까지 멈추기 위해 return;기존 방법을 일찍 사용 하는 방법.


반복이 20ms 이상 걸린 후에 만 ​​코 루틴을 사용하여 산출하는 방법이 있습니까?
DarkDestry

@DarkDestry 스톱워치 인스턴스를 사용하여 내부 루프에서 소비하는 시간을 정하고 스톱워치를 재설정하고 내부 루프를 다시 시작하기 전에 생성하는 외부 루프로 나눌 수 있습니다.
DMGregory

1
좋은 대답입니다! 코 루틴을 사용해야하는 또 하나의 이유는 webgl을 빌드해야 할 경우 스레드를 사용하면 작동하지 않는 것입니다. 거대한 프로젝트에서 지금 두통으로 앉아 : P
Mikael Högström

좋은 답변과 감사합니다. 스레드를 여러 번 중지했다가 다시 시작해야한다면 어떻게해야할지 궁금해하고 중단을 시도하고 오류가 발생합니다
flankechen

@flankechen 스레드 시작 및 해제는 상대적으로 비용이 많이 드는 작업이므로 스레드를 사용할 수는 있지만 휴면 상태를 유지하는 것이 좋습니다. 세마포어 나 모니터 같은 것을 사용하여 새로운 작업을 할 때 신호를 보냅니다. 유스 케이스를 자세히 설명하는 새로운 질문을 게시하고 싶은 경우이를 달성하는 효율적인 방법을 제안 할 수 있습니까?
DMGregory

0

무거운 계산을 다른 스레드에 넣을 수는 있지만 Unity의 API는 스레드 안전하지 않으므로 기본 스레드에서 실행해야합니다.

Asset Store에서이 패키지를 사용 해보면 스레딩을보다 쉽게 ​​사용할 수 있습니다. http://u3d.as/wQg 한 줄의 코드 만 사용하여 스레드를 시작하고 Unity API를 안전하게 실행할 수 있습니다.



0

@DMGregory가 정말 잘 설명했습니다.

스레딩과 코 루틴을 모두 사용할 수 있습니다. 이전에는 메인 스레드를 오프로드하고 나중에 스레드를 메인으로 제어를 되돌립니다. 무거운 실을 분리하여 실을 분리하는 것이 더 합리적입니다. 따라서 작업 대기 행렬이 사용자가 수행 한 것일 수 있습니다.

Unity Wiki 에는 실제로 좋은 JobQueue 예제 스크립트가 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.