Làm thế nào để không đóng băng chủ đề chính trong Unity?


33

Tôi có một thuật toán tạo mức độ nặng tính toán. Như vậy, gọi nó luôn luôn dẫn đến màn hình trò chơi đóng băng. Làm cách nào tôi có thể đặt chức năng trên một luồng thứ hai trong khi trò chơi vẫn tiếp tục hiển thị màn hình đang tải để cho biết trò chơi không bị đóng băng?


1
Bạn có thể sử dụng hệ thống luồng để chạy các công việc khác trong nền ... nhưng họ có thể không gọi bất cứ thứ gì trong Unity API vì công cụ đó không an toàn. Chỉ nhận xét vì tôi chưa làm điều này và không thể tự tin cung cấp cho bạn mã ví dụ một cách nhanh chóng.
Almo

Sử dụng Listcác hành động để lưu trữ các chức năng bạn muốn gọi trong luồng chính. khóa và sao chép Actiondanh sách trong Updatehàm vào danh sách tạm thời, xóa danh sách gốc sau đó thực thi Actionmã trong đó Listtrên luồng chính. Xem UnityThread từ bài đăng khác của tôi về cách làm điều này. Ví dụ: để gọi một chức năng trên Chủ đề chính, UnityThread.executeInUpdate(() => { transform.Rotate(new Vector3(0f, 90f, 0f)); });
Lập trình viên

Câu trả lời:


48

Cập nhật: Năm 2018, Unity tung ra Hệ thống công việc C # như một cách để giảm tải công việc và sử dụng nhiều lõi CPU.

Câu trả lời dưới đây có trước hệ thống này. Nó vẫn sẽ hoạt động, nhưng có thể có các tùy chọn tốt hơn có sẵn trong Unity hiện đại, tùy thuộc vào nhu cầu của bạn. Cụ thể, hệ thống công việc dường như giải quyết một số hạn chế về những chủ đề được tạo thủ công có thể truy cập một cách an toàn, được mô tả bên dưới. Các nhà phát triển thử nghiệm với báo cáo xem trước thực hiện các chương trình phát sóng và xây dựng các lưới song song , ví dụ.

Tôi muốn mời người dùng có kinh nghiệm sử dụng hệ thống công việc này để thêm câu trả lời của riêng họ phản ánh trạng thái hiện tại của động cơ.


Trước đây, tôi đã sử dụng phân luồng cho các tác vụ nặng trong Unity (thường là xử lý hình ảnh & hình học) và nó không khác nhiều so với sử dụng các luồng trong các ứng dụng C # khác, với hai cảnh báo:

  1. Bởi vì Unity sử dụng một tập hợp con cũ hơn của .NET, nên có một số tính năng và thư viện luồng mới hơn mà chúng ta không thể sử dụng, nhưng những điều cơ bản đều có ở đó.

  2. Như Almo lưu ý trong một nhận xét ở trên, nhiều loại Unity không phải là chủ đề an toàn và sẽ đưa ra ngoại lệ nếu bạn cố gắng xây dựng, sử dụng hoặc thậm chí so sánh chúng với luồng chính. Những điều cần lưu ý:

    • Một trường hợp phổ biến là kiểm tra xem liệu tham chiếu GameObject hoặc Monobehaviour có bị hủy hay không trước khi thử truy cập các thành viên của nó. myUnityObject == nullgọi một toán tử bị quá tải cho bất cứ thứ gì xuất phát từ UnityEngine.Object, nhưng System.Object.ReferenceEquals()hoạt động ở mức độ này - chỉ cần nhớ rằng GameObject của Dest () so sánh với null bằng cách sử dụng quá tải, nhưng chưa phải là ReferenceEqual thành null.

    • Đọc các tham số từ các loại Unity thường an toàn trên một luồng khác (trong đó nó sẽ không ngay lập tức đưa ra một ngoại lệ miễn là bạn cẩn thận kiểm tra null như trên), nhưng lưu ý cảnh báo của Philipp ở đây rằng luồng chính có thể đang sửa đổi trạng thái trong khi bạn đang đọc nó Bạn sẽ cần phải xử lý kỷ luật về việc ai được phép sửa đổi những gì và khi nào để tránh đọc một số trạng thái không nhất quán, điều này có thể dẫn đến các lỗi có thể rất khó theo dõi vì chúng phụ thuộc vào thời gian dưới một phần nghìn giây giữa các luồng mà chúng ta có thể 'T tái sản xuất theo ý muốn.

    • Thành viên tĩnh ngẫu nhiên và thời gian không có sẵn. Tạo một thể hiện của System.Random trên mỗi luồng nếu bạn cần ngẫu nhiên và System.Diagnostics.Stopwatch nếu bạn cần thông tin về thời gian.

    • Các hàm Mathf, Vector, Ma trận, Đệ tứ và các cấu trúc màu đều hoạt động tốt trên các luồng, do đó bạn có thể thực hiện hầu hết các tính toán của mình một cách riêng biệt

    • Tạo GameObjects, đính kèm Monobehaviours hoặc tạo / cập nhật Hoạ tiết, Lưới, Vật liệu, v.v ... tất cả đều cần phải xảy ra trên luồng chính. Trước đây khi tôi cần làm việc với những thứ này, tôi đã thiết lập một hàng đợi nhà sản xuất, nơi luồng công nhân của tôi chuẩn bị dữ liệu thô (như một mảng lớn các vectơ / màu sắc để áp dụng cho lưới hoặc kết cấu), và Cập nhật hoặc Coroutine trên các cuộc thăm dò chủ đề chính cho dữ liệu và áp dụng nó.

Với những lưu ý đó, đây là mẫu tôi thường sử dụng cho công việc theo luồng. Tôi không đảm bảo rằng đó là một phong cách thực hành tốt nhất, nhưng nó hoàn thành công việc. (Nhận xét hoặc chỉnh sửa để cải thiện đều được chào đón - Tôi biết luồng là một chủ đề rất sâu sắc mà tôi chỉ biết những điều cơ bản)

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.
    }
}

Nếu bạn không nhất thiết phải phân chia công việc giữa các luồng để tăng tốc và bạn chỉ đang tìm cách làm cho nó không bị chặn để phần còn lại của trò chơi của bạn tiếp tục tích tắc, một giải pháp nhẹ hơn trong Unity là Coroutines . Đây là những chức năng có thể thực hiện một số công việc sau đó mang lại quyền kiểm soát cho động cơ để tiếp tục những gì nó đang làm và tiếp tục hoạt động liên tục sau đó.

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...
        }
    }
}

Điều này không cần bất kỳ cân nhắc dọn dẹp đặc biệt nào, vì động cơ (theo như tôi có thể nói) đã loại bỏ các xác chết khỏi các vật thể bị phá hủy cho bạn.

Tất cả trạng thái cục bộ của phương thức được bảo toàn khi nó sinh ra và tiếp tục, vì vậy trong nhiều mục đích, nó như thể nó đang chạy không bị gián đoạn trên một luồng khác (nhưng bạn có tất cả các tiện ích để chạy trên luồng chính). Bạn chỉ cần đảm bảo rằng mỗi lần lặp của nó đủ ngắn để nó không làm chậm luồng chính của bạn một cách vô lý.

Bằng cách đảm bảo các hoạt động quan trọng không bị tách biệt bởi một sản lượng, bạn có thể có được sự thống nhất của hành vi đơn luồng - biết rằng không có tập lệnh hoặc hệ thống nào khác trên luồng chính có thể sửa đổi dữ liệu mà bạn đang làm việc.

Dòng lợi nhuận mang lại cho bạn một vài lựa chọn. Bạn có thể...

  • yield return null để tiếp tục sau Cập nhật của khung tiếp theo ()
  • yield return new WaitForFixedUpdate() để tiếp tục sau FixedUpdate () tiếp theo
  • yield return new WaitForSeconds(delay) để tiếp tục sau một khoảng thời gian trò chơi nhất định
  • yield return new WaitForEndOfFrame() để tiếp tục sau khi GUI kết thúc kết xuất
  • yield return myRequestnơi myRequestlà một WWW dụ, để tiếp tục một khi dữ liệu được yêu cầu kết thúc tải từ web hoặc đĩa.
  • yield return otherCoroutinenơi otherCoroutinelà một ví dụ coroutine , để tiếp tục sau khi otherCoroutinehoàn tất. Điều này thường được sử dụng trong các hình thức yield return StartCoroutine(OtherCoroutineMethod())để thực hiện chuỗi đến một coroutine mới có thể tự mang lại khi nó muốn.

    • Thực nghiệm, bỏ qua phần thứ hai StartCoroutinevà chỉ đơn giản là viết yield return OtherCoroutineMethod()hoàn thành cùng một mục tiêu nếu bạn muốn thực hiện chuỗi trong cùng một bối cảnh.

      Việc bọc bên trong StartCoroutinevẫn có thể hữu ích nếu bạn muốn chạy coroutine lồng nhau kết hợp với một đối tượng thứ hai, nhưyield return otherObject.StartCoroutine(OtherObjectsCoroutineMethod())

... tùy thuộc vào thời điểm bạn muốn coroutine thực hiện lượt tiếp theo.

Hoặc yield break;để ngăn chặn coroutine trước khi nó kết thúc, cách bạn có thể sử dụng return;để sớm ra khỏi một phương pháp thông thường.


Có cách nào để sử dụng coroutines chỉ mang lại hiệu quả sau khi lặp lại mất hơn 20ms không?
DarkDestry

@DarkDestry Bạn có thể sử dụng một ví dụ đồng hồ bấm giờ để tính thời gian bạn sử dụng trong vòng lặp bên trong của mình và thoát ra một vòng lặp bên ngoài nơi bạn nhường trước khi đặt lại đồng hồ bấm giờ và tiếp tục vòng lặp bên trong.
DMGregory

1
Câu trả lời tốt! Tôi chỉ muốn thêm rằng một lý do nữa để sử dụng coroutines là nếu bạn cần xây dựng cho webgl thì nó sẽ hoạt động nếu nó không sử dụng chủ đề. Ngồi với cơn đau đầu đó trong một dự án lớn ngay bây giờ: P
Mikael Högström

Câu trả lời tốt và cảm ơn. Chỉ cần tự hỏi điều gì sẽ xảy ra nếu tôi cần dừng và khởi động lại chuỗi nhiều lần, tôi thử hủy bỏ và bắt đầu, nhận lỗi cho tôi
flankechen

@flankechen Khởi động và xé dán luồng là các hoạt động tương đối tốn kém, vì vậy, chúng tôi thường muốn giữ luồng có sẵn nhưng không hoạt động - sử dụng những thứ như semaphores hoặc màn hình để báo hiệu khi chúng tôi có công việc mới để thực hiện. Bạn muốn đăng một câu hỏi mới mô tả chi tiết trường hợp sử dụng của bạn và mọi người có thể đề xuất các cách hiệu quả để đạt được nó?
DMGregory

0

Bạn có thể đặt phép tính nặng nề của mình vào một luồng khác, nhưng API của Unity không an toàn cho luồng, bạn phải thực hiện chúng trong luồng chính.

Vâng, bạn có thể thử gói này trên Asset Store sẽ giúp bạn sử dụng luồng dễ dàng hơn. http://u3d.as/wQg Bạn chỉ có thể sử dụng một dòng mã để bắt đầu một chuỗi và thực thi API Unity một cách an toàn.



0

@DMGregory giải thích nó thực sự tốt.

Cả luồng và coroutines có thể được sử dụng. Trước đây để giảm tải chủ đề chính và sau đó để trả lại quyền kiểm soát cho chủ đề chính. Đẩy vật nặng để tách sợi có vẻ hợp lý hơn. Do đó, hàng đợi công việc có thể là những gì bạn đang theo đuổi.

Có kịch bản ví dụ JobQueue thực sự tốt trên Unity Wiki.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.