[Unity] 在puerts中使用js多线程(伪)

版本信息:

puerts版本: unity_plugin_v9        javascript引擎: v8

unity版本: 2019.4.8f1

本文开源: https://github.com/layerpsr/worker_for_unity_ts

大致思路: 

  1. 每个线程拥有自己的JsEnv实例;
  2. 线程之间通过JsWorker.cs实例传递数据丶消息;
  3. 如果js object想要传给其他线程, 需要将它封装为C# object让JsWorker.cs进行分发;

对象的传递:

  • 线程1将js object封装为Package.cs对象(仅允许js值类型拷贝和C# object的引用);
  • 线程1将封装的Package.cs对象通过JsWorker.cs分发给线程2;
  • 线程2从JsWorker.cs拿到Package.cs对象, 在js拆包还原为js object;

注: 建议仅传递纯数据, 原因如下:

  • js object的原型链无法进行传递;
  • js function将序列化为字符串, 使用eval()还原, 闭包引用无法传递 ;

例子代码:

/**
* main.ts文件
*/
import * as CS from "csharp";

const id = CS.System.Threading.Thread.CurrentThread.ManagedThreadId;
const threadName = `Main(${id})\t`;

//此处为实现的Puerts.ILoader类
let worker = new JsWorker(CS.JsManager.GetInstance().Loader);
worker.on("main_on", () => "this is main thread");
worker.on("data", data => {
    if (typeof data == "function") {
        console.log(threadName, data.toString());
    }
    else if (typeof data == "object") {
        console.log(threadName, JSON.stringify(data));
    }
    else
        console.log(threadName, data);
});

worker.start("./test");

worker.post("data", { msg: "this main thread message" });
//JsWorker在JsEnv初始化时需要主线程调用, 所以此处不能调用阻塞方法
setTimeout(() => {
    console.log(threadName, worker.postSync("child_on"))
}, 1000);
/**
* test.ts文件
*/

import * as CS from "csharp";

const id = CS.System.Threading.Thread.CurrentThread.ManagedThreadId;
const threadName = `Child(${id})\t`;

globalWorker.on("child_on", () => "this is child thread");
globalWorker.on("data", data => {
    if (typeof data == "function") {
        console.log(threadName, data.toString());
    }
    else if (typeof data == "object") {
        console.log(threadName, JSON.stringify(data));
    }
    else
        console.log(threadName, data);
});

globalWorker.post("data", { msg: "this child thread message" });
console.log(threadName, globalWorker.postSync("main_on"));

setTimeout(() => {
    let i = 3;
    while (i-- > 0) {
        globalWorker.post("data", { msg: "this is child thread message", index: i });
        CS.System.Threading.Thread.Sleep(1000);
    }
}, 1000);

打印日志如下:

关键代码:

/**
* jsWorker.ts
*/

import * as CS from "csharp";
import { $generic } from "puerts";
let List = $generic(CS.System.Collections.Generic.List$1, CS.System.Object);

const CLOSE_EVENT = "close";

class JsWorker {
    public get isAlive() { return this.worker.IsAlive; }
    public readonly isMain: boolean;
    private readonly worker: CS.JsWorker;
    private readonly callbacks: Map<string, ((data?: any) => void)[]>;
    constructor(loader: CS.Puerts.ILoader | CS.JsWorker) {
        let worker: CS.JsWorker = undefined;
        if (loader instanceof CS.JsWorker) {
            worker = loader;
            this.isMain = false;
        } else {
            worker = CS.JsWorker.New(loader);
            this.isMain = true;
        }
        this.worker = worker;
        this.callbacks = new Map();
        this.working();
    }
    private working() {
        let getValue = (data: CS.JsWorker.Package) => {
            if (data !== undefined && data !== null && data !== void 0) {
                return this.unpackage(data);
            }
            return undefined;
        };
        let onmessage = (name: string, data: CS.JsWorker.Package): CS.JsWorker.Package => {
            let result = undefined;
            let arr = this.callbacks.get(name);
            if (arr) {
                let o = getValue(data);
                for (let cb of arr) {
                    result = cb(o);
                }
            }
            if (result !== undefined && result !== null && result !== void 0)
                return this.package(result);
            return undefined;
        };
        if (this.isMain)
            this.worker.messageByMain = (name, data) => {
                if (name === CLOSE_EVENT) {
                    let o = getValue(data), closing = true;
                    let arr = this.callbacks.get(name);
                    if (arr)
                        arr.forEach(cb => {
                            if ((cb as (data?: any) => boolean)(o) === false)
                                closing = false;
                        });
                    if (closing)
                        this.dispose();
                    return this.package(closing);
                } else
                    return onmessage(name, data);
            };
        else
            this.worker.messageByChild = onmessage;
    }
    private package(data: any, refs?: WeakMap<object, number>, refId?: number): CS.JsWorker.Package {
        refId = refId ?? 1;
        refs = refs ?? new WeakMap();

        let result = new CS.JsWorker.Package();
        let type = typeof (data);
        if ((type === "object" || type === "function") && refs.has(data)) {
            result.type = CS.JsWorker.Type.RefObject;
            result.value = refs.get(data);
        }
        else {
            switch (type) {
                case "object":
                    {
                        //添加引用
                        let id = refId++;
                        result.id = id;
                        refs.set(data, id);
                        //创建C#对象
                        if (data instanceof CS.System.Object) {
                            result.type = CS.JsWorker.Type.Value;
                            result.value = data;
                        }
                        else if (data instanceof ArrayBuffer) {
                            result.type = CS.JsWorker.Type.ArrayBuffer;
                            result.value = CS.JsWorker.Package.ToBytes(data);
                        }
                        else if (Array.isArray(data)) {
                            let list = new List() as CS.System.Collections.Generic.List$1<any>;
                            for (let i = 0; i < data.length; i++) {
                                let item = this.package(data[i], refs, refId);
                                item.info = i;
                                list.Add(item);
                            }
                            result.type = CS.JsWorker.Type.Array;
                            result.value = list;
                        }
                        else {
                            let list = new List() as CS.System.Collections.Generic.List$1<any>;
                            Object.keys(data).forEach(key => {
                                let item = this.package(data[key], refs, refId);
                                item.info = key;
                                list.Add(item);
                            });
                            result.type = CS.JsWorker.Type.Object;
                            result.value = list;
                        }
                    }
                    break;
                case "function":
                    {
                        //添加引用
                        let id = refId++;
                        result.id = id;
                        refs.set(data, id);
                        //创建C#对象
                        result.type = CS.JsWorker.Type.Function;
                        result.value = data.toString();
                    }
                    break;
                case "string":
                case "number":
                case "bigint":
                case "boolean":
                    result.type = CS.JsWorker.Type.Value;
                    result.value = data;
                    break;
                default:
                    result.type = CS.JsWorker.Type.Unknown;
                    break;
            }
        }
        return result;
    }
    private unpackage(data: CS.JsWorker.Package, refs?: Map<number, Object>): any {
        refs = refs ?? new Map();
        let result = undefined, id = data.id, value = data.value;
        switch (data.type) {
            case CS.JsWorker.Type.Object:
                {
                    result = {};
                    if (id > 0) refs.set(id, result); //Add ref object
                    let arr = value as CS.System.Collections.Generic.List$1<CS.JsWorker.Package>;
                    for (let i = 0; i < arr.Count; i++) {
                        let item = arr.get_Item(i);
                        result[item.info] = this.unpackage(item, refs);
                    }
                }
                break;
            case CS.JsWorker.Type.Array:
                {
                    result = [];
                    if (id > 0) refs.set(id, result); //Add ref object
                    let arr = value as CS.System.Collections.Generic.List$1<CS.JsWorker.Package>;
                    for (let i = 0; i < arr.Count; i++) {
                        let item = arr.get_Item(i);
                        result[item.info] = this.unpackage(item, refs);
                    }
                }
                break;
            case CS.JsWorker.Type.ArrayBuffer:
                result = CS.JsWorker.Package.ToArrayBuffer(value);
                if (id > 0) refs.set(id, result); //Add ref object
                break;
            case CS.JsWorker.Type.Function:
                result = eval(value);
                if (id > 0) refs.set(id, result); //Add ref object
                break;
            case CS.JsWorker.Type.RefObject:
                if (refs.has(value))
                    result = refs.get(value);
                else
                    result = "Error: ref id " + value + " not found";
                break;
            case CS.JsWorker.Type.Unknown:
            default:
                result = value;
                if (id > 0) refs.set(id, result); //Add ref object
                break;
        }
        return result;
    }
    public start(filepath: string) {
        if (globalWorker && globalWorker["worker"] == this.worker)
            throw new Error("Thread cannot called start");

        this.worker.Startup(filepath);
    }
    public dispose() {
        if (globalWorker && globalWorker["worker"] == this.worker)
            this.post(CLOSE_EVENT);
        else {
            this.worker.Dispose();
            this.callbacks.clear();
        }
    }
    public post(eventName: string, data?: any) {
        let o: CS.JsWorker.Package;
        if (data !== undefined && data !== null && data !== void 0) {
            o = this.package(data);
        }
        if (this.isMain)
            this.worker.CallChild(eventName, o);
        else
            this.worker.CallMain(eventName, o);
    }
    public postSync<T>(eventName: string, data?: any): T {
        let o: CS.JsWorker.Package, result = undefined;
        if (data !== undefined && data !== null && data !== void 0) {
            o = this.package(data);
        }
        if (this.isMain)
            result = this.worker.Sync.CallChild(eventName, o);
        else
            result = this.worker.Sync.CallMain(eventName, o);
        //Result
        if (result !== undefined && result !== null && result !== void 0) {
            result = this.unpackage(result);
        }
        return result;
    }
    public eval(chunk: string, chunkName?: string) {
        if (globalWorker && globalWorker["worker"] == this.worker)
            throw new Error("Thread cannot called eval");

        this.worker.Eval(chunk, chunkName);
    }
    public on(eventName: string, cb: (data?: any) => void) {
        if (eventName && cb) {
            let arr = this.callbacks.get(eventName);
            if (!arr) {
                arr = [];
                this.callbacks.set(eventName, arr);
            }
            arr.push(cb);
        }
    }
    public remove(eventName: string, cb: (data?: any) => void) {
        let arr = this.callbacks.get(eventName);
        if (arr) {
            let index = arr.indexOf(cb);
            if (index >= 0)
                this.callbacks.set(eventName, [...arr.slice(0, index), ...arr.slice(index + 1)]);
        }
    }
    public removeAll(eventName?: string) {
        if (eventName)
            this.callbacks.delete(eventName);
        else
            this.callbacks.clear();
    }
}
(function () {
    let _this = (this ?? globalThis);
    _this["JsWorker"] = JsWorker;
    _this["globalWorker"] = undefined;
})();

/**
 * 接口声明
 */
declare global {
    class JsWorker {
        public get isAlive(): boolean;
        public readonly isMain: boolean;
        public constructor(loader: CS.Puerts.ILoader);
        /**
         * 开始执行脚本(实例生命周期内仅调用一次) 
         */
        public start(filepath: string): void;
        /**
         * 关闭JsWorker实例, 不可在内部关闭实例
         */
        public dispose(): void;
        /**
         * 发送一条消息(异步)
         */
        public post(eventName: string, data?: any): void;
        /**
         * 同步发送消息并获取返回值
         */
        public postSync<T>(eventName: string, data?: any): T;
        /**
         * 执行一段代码, 由外部程序调用
         */
        public eval(chunk: string, chunkName?: string): void;
        /**
         * 监听事件信息
         */
        public on(eventName: string, cb: (data?: any) => void): void;
        /**
         * 监听并劫持JsWorker实例close消息
         */
        public on(eventName: "close", cb: (state?: any) => boolean): void;
        /**
         * 移除一条监听
         */
        public remove(eventName: string, cb: (data?: any) => void): void;
        /**
         * 移除所有监听
         */
        public removeAll(eventName: string): void;
        /**
         * 移除所有监听
         */
        public removeAll(): void;
    }
    /**
     * 只能在JsWorker线程内部访问, 与主线程交互的对象
     */
    const globalWorker: JsWorker;
}

 

/**
* JsWorker.cs
*/

using System;
using System.Collections.Generic;
using System.Threading;
using Puerts;
using UnityEngine;

public class JsWorker : MonoBehaviour, IDisposable
{
    public static JsWorker New(ILoader loader, string filepath)
    {
        var obj = new GameObject("JsWorker");
        DontDestroyOnLoad(obj);
        var ins = obj.AddComponent<JsWorker>();
        ins.loader = new SyncLoader(ins, loader);
        if (!string.IsNullOrEmpty(filepath))
            ins.Working(filepath);

        return ins;
    }
    public static JsWorker New(ILoader loader)
    {
        return New(loader, null);
    }

    /// <summary>
    /// jsWorker.ts脚本
    /// </summary>
    private const string JS_WORKER = "require('./common/jsWorker')";
    /// <summary>
    /// 每次处理的事件
    /// </summary>
    private const int PROCESS_COUNT = 5;
    /// <summary>
    /// Unity主线程
    /// </summary>
    private static readonly int MAIN_THREAD_ID = Thread.CurrentThread.ManagedThreadId;

    public JsEnv JsEnv { get; private set; }
    //消息接口
    public Func<string, Package, Package> messageByMain { get; set; }
    public Func<string, Package, Package> messageByChild { get; set; }
    //线程初始完成, 且运行中
    public bool IsAlive
    {
        get
        {
            return this.thread != null && this.thread.IsAlive && this.JsEnv != null;
        }
    }
    //同步对象
    private SyncLoader loader;
    private SyncProcess sync;
    public SyncProcess Sync
    {
        get
        {
            if (!this.IsAlive)
                throw new Exception("Thread not ready ok, can't use it now.");
            return this.sync;
        }
    }
    public ReaderWriterLock locker;
    private const int lockTimeout = 1000;
    //同步状态
    private bool syncing;
    //线程
    private Thread thread;
    private bool running = false;
    //消息集合
    private Queue<Event> mainEvents;
    private Queue<Event> childEvents;
    //Eval require list
    private Queue<(string, string)> eval;

    private JsWorker()
    {
        mainEvents = new Queue<Event>();
        childEvents = new Queue<Event>();
        eval = new Queue<(string, string)>();
        sync = new SyncProcess(this);
        locker = new ReaderWriterLock();
    }
    void Start()
    {
        if (loader == null)
        {
            this.enabled = false;
            throw new Exception("instance cannot working, loader is null");
        }
    }
    void Update()
    {
        ProcessMain();
        sync.ProcessMain();
        loader.ProcessMain();
    }
    void OnDestroy()
    {
        Dispose();
    }
    void Working(string filepath)
    {
        if (this.JsEnv != null || this.thread != null || this.running)
            throw new Exception("Thread is running, cannot start repeatedly!");
        if (this.loader == null)
            throw new Exception("Thread cannot start working, loader is null!");
        if (!this.enabled)
            throw new Exception("Thread cannot start working, main thread is disable");

        syncing = false;
        running = true;
        thread = new Thread(new ThreadStart(() =>
        {
            JsEnv jsEnv = null;
            try
            {
                // JsEnv脚本放在Resource目录下,故ILoader仅允许在主线程调用
                // 子线程_SyncLoader接口会阻塞线程, 直到主线程调用ILoader后才会继续执行
                // JsEnv初始化时将调用_SyncLoader接口
                jsEnv = JsEnv = new JsEnv(loader);
                jsEnv.UsingAction<string, string>();
                jsEnv.UsingAction<string, Package>();
                jsEnv.UsingFunc<string, Package, object>();
                jsEnv.UsingFunc<string, Package, Package>();
                jsEnv.Eval(JS_WORKER);
                jsEnv.Eval<Action<JsWorker>>(@"(function (_w){ (this ?? globalThis)['globalWorker'] = new JsWorker(_w); })")(this);
                jsEnv.Eval(string.Format("require(\"{0}\")", filepath));
                while (running)
                {
                    if (JsEnv == null)
                        break;
                    Thread.Sleep(20);
                    jsEnv.Tick();
                    ProcessChild();
                    ProcessChildEval(jsEnv);
                    sync.ProcessChild();
                }
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogWarning(e.Message);
            }
            finally
            {
                jsEnv?.Dispose();
                JsEnv = null;
            }
        }));
        thread.IsBackground = true;
        thread.Start();
    }
    public void VerifySafety(bool isMainThread)
    {
        var id = Thread.CurrentThread.ManagedThreadId;
        if (isMainThread && id != MAIN_THREAD_ID || !isMainThread && id == MAIN_THREAD_ID)
            throw new Exception("Incorrect in thread");
    }
    public void Startup(string filepath)
    {
        Working(filepath);
    }
    public void Dispose()
    {
        messageByMain = null;
        messageByChild = null;
        running = false;
        //此处仅通知线程中断, 由线程自行结束(使用Abort阻塞将导致puerts crash)
        if (thread != null) thread.Interrupt();
        //if (JsEnv != null) JsEnv.Dispose();
        JsEnv = null;
        thread = null;
    }
    public void CallMain(string name, Package data)
    {
        lock (mainEvents)
        {
            mainEvents.Enqueue(new Event()
            {
                name = name,
                data = data
            });
        }
    }
    public void CallChild(string name, Package data)
    {
        lock (childEvents)
        {
            childEvents.Enqueue(new Event()
            {
                name = name,
                data = data
            });
        }
    }
    public void Eval(string chunk, string chunkName = "chunk")
    {
        if (chunk == null)
            return;
        lock (eval)
        {
            eval.Enqueue((chunk, chunkName));
        }
    }
    private void ProcessMain()
    {
        if (mainEvents.Count > 0)
        {
            List<Event> events = new List<Event>();
            lock (mainEvents)
            {
                int count = PROCESS_COUNT;
                while (count-- > 0 && mainEvents.Count > 0)
                    events.Add(mainEvents.Dequeue());
            }
            Func<string, Package, Package> func = this.messageByMain;
            if (func != null)
            {
                for (int i = 0; i < events.Count; i++)
                {
                    try
                    {
                        func(events[i].name, events[i].data);
                    }
                    catch (Exception e)
                    {
                        UnityEngine.Debug.LogError(e.Message);
                    }
                }
            }
        }
    }
    private void ProcessChild()
    {
        if (childEvents.Count > 0)
        {
            List<Event> events = new List<Event>();
            lock (childEvents)
            {
                int count = PROCESS_COUNT;
                while (count-- > 0 && childEvents.Count > 0)
                    events.Add(childEvents.Dequeue());
            }
            Func<string, Package, Package> func = this.messageByChild;
            if (func != null)
            {
                for (int i = 0; i < events.Count; i++)
                {
                    try
                    {
                        func(events[i].name, events[i].data);
                    }
                    catch (Exception e)
                    {
                        UnityEngine.Debug.LogError(e.Message);
                    }
                }
            }
        }
    }
    private void ProcessChildEval(JsEnv jsEnv)
    {
        if (eval.Count > 0)
        {
            List<(string, string)> chunks = new List<(string, string)>();
            lock (eval)
            {
                int count = PROCESS_COUNT;
                while (count-- > 0 && eval.Count > 0)
                    chunks.Add(eval.Dequeue());
            }
            for (int i = 0; i < chunks.Count; i++)
            {
                try
                {
                    var chunk = chunks[i];
                    jsEnv.Eval(chunk.Item1, chunk.Item2);
                }
                catch (Exception e)
                {
                    UnityEngine.Debug.LogError(e.Message);
                }
            }
        }
    }
    private void ProcessAsyncing()
    {
        if (Thread.CurrentThread.ManagedThreadId == MAIN_THREAD_ID)
        {
            sync.ProcessMain();
            loader.ProcessMain();
        }
        else
            sync.ProcessChild();
    }
    /// <summary>
    /// 获取同步锁定, 返回是否成功
    /// (注:如果两条线程都锁定则会死锁(它们都在等待对方同步), 因此只能有一条线程锁定同步状态)
    /// </summary>
    internal bool AcquireSyncing()
    {
        var timeout = DateTime.Now + TimeSpan.FromMilliseconds(lockTimeout);
        //如果未处于同步中直接返回, 否则同步后等待状态更新
        while (DateTime.Now <= timeout)
        {
            //请求锁
            locker.AcquireWriterLock(lockTimeout);
            if (!this.syncing)
            {
                this.syncing = true;
                locker.ReleaseWriterLock();
                return true;
            }
            ProcessAsyncing();
            //释放锁
            locker.ReleaseWriterLock();
            Thread.Sleep(20);
        }
        return false;
    }
    /// <summary> 释放同步锁定 </summary>
    internal void ReleaseSyncing()
    {
        locker.AcquireWriterLock(lockTimeout);
        this.syncing = false;
        locker.ReleaseWriterLock();
    }


    private class Event
    {
        public string name;
        public Package data;
    }
    private class SyncLoader : ILoader
    {
        private JsWorker worker = null;
        //脚本缓存
        private Dictionary<string, string> scripts;
        private Dictionary<string, string> debugPaths;
        private Dictionary<string, bool> state;
        //这个ILoader只能在主线程调用, 而本实例化对象在子线程中使用需要通过主线程同步
        private ILoader loader;
        //线程安全
        private ReaderWriterLock locker = new ReaderWriterLock();
        private const int lockTimeout = 1000;
        //加载内容
        private string filePath = null;
        private bool fileExists = false;
        private string readPath = null;
        private string readContent = null;
        private string debugpath = null;

        public SyncLoader(JsWorker worker, ILoader loader)
        {
            this.worker = worker;
            this.loader = loader;
            this.scripts = new Dictionary<string, string>();
            this.debugPaths = new Dictionary<string, string>();
            this.state = new Dictionary<string, bool>();
        }

        public bool FileExists(string filepath)
        {
            bool result = false;
            if (this.state.TryGetValue(filepath, out result))
                return result;
            //获取同步状态
            if (!worker.AcquireSyncing())
                throw new Exception("Other thread is syncing!");
            //写入主线程
            locker.AcquireWriterLock(lockTimeout);
            this.filePath = filepath;
            this.fileExists = false;
            locker.ReleaseWriterLock();
            //等待主线程同步
            try
            {
                while (true)
                {
                    locker.AcquireReaderLock(lockTimeout);
                    if (this.filePath == null)
                        break;
                    locker.ReleaseReaderLock();
                }
                this.state.Add(filepath, this.fileExists);
                return this.fileExists;
            }
            finally
            {
                locker.ReleaseReaderLock();
                worker.ReleaseSyncing();
            }
        }
        public string ReadFile(string filepath, out string debugpath)
        {
            string script = null;
            if (this.scripts.TryGetValue(filepath, out script))
            {
                debugpath = this.debugPaths[filepath];
                return script;
            }
            //获取同步状态
            if (!worker.AcquireSyncing())
                throw new Exception("Other thread is syncing!");
            //写入主线程
            locker.AcquireWriterLock(lockTimeout);
            this.readPath = filepath;
            this.readContent = null;
            this.debugpath = null;
            locker.ReleaseWriterLock();
            //等待主线程同步
            try
            {
                while (true)
                {
                    locker.AcquireReaderLock(lockTimeout);
                    if (this.readPath == null)
                        break;
                    locker.ReleaseReaderLock();
                }
                this.scripts.Add(filepath, this.readContent);
                this.debugPaths.Add(filepath, this.debugpath);

                debugpath = this.debugpath;
                return this.readContent;
            }
            finally
            {
                locker.ReleaseReaderLock();
                worker.ReleaseSyncing();
            }
        }

        public void ProcessMain()
        {
            if (this.filePath != null || this.readPath != null)
            {
                try
                {
                    locker.AcquireWriterLock(lockTimeout);
                    if (this.filePath != null)
                    {
                        this.fileExists = loader.FileExists(this.filePath);
                        this.filePath = null;
                    }
                    if (this.readPath != null)
                    {
                        this.readContent = loader.ReadFile(this.readPath, out this.debugpath);
                        this.readPath = null;
                    }
                }
                catch (Exception e)
                {
                    this.filePath = null;
                    this.fileExists = false;
                    this.readPath = null;
                    this.readContent = null;
                    this.debugpath = null;
                    throw e;
                }
                finally
                {
                    locker.ReleaseWriterLock();
                }
            }
        }
    }
    public class SyncProcess
    {
        private JsWorker worker = null;
        //线程安全
        private ReaderWriterLock locker = new ReaderWriterLock();
        private const int lockTimeout = 1000;
        //同步消息
        private string m_eventName = null;
        private Package m_eventData = null;
        private string c_eventName = null;
        private Package c_eventData = null;

        public SyncProcess(JsWorker worker)
        {
            this.worker = worker;
        }

        public object CallMain(string name, Package data, bool throwOnError = true)
        {
            if (name == null) return null;
            //获取同步状态
            if (!worker.AcquireSyncing())
            {
                if (!throwOnError) return null;
                throw new Exception("Other thread is syncing!");
            }
            //写入主线程
            locker.AcquireWriterLock(lockTimeout);
            this.m_eventName = name;
            this.m_eventData = data;
            locker.ReleaseWriterLock();
            //等待主线程同步
            try
            {
                while (true)
                {
                    locker.AcquireReaderLock(lockTimeout);
                    if (this.m_eventName == null)
                        break;
                    locker.ReleaseReaderLock();
                }
                return this.m_eventData;
            }
            finally
            {
                locker.ReleaseReaderLock();
                worker.ReleaseSyncing();
            }
        }
        public object CallChild(string name, Package data, bool throwOnError = true)
        {
            if (name == null) return null;
            //获取同步状态
            if (!worker.AcquireSyncing())
            {
                if (!throwOnError) return null;
                throw new Exception("Other thread is syncing!");
            }
            //写入子线程
            locker.AcquireWriterLock(lockTimeout);
            this.c_eventName = name;
            this.c_eventData = data;
            locker.ReleaseWriterLock();
            //等待子线程同步
            try
            {
                while (true)
                {
                    locker.AcquireReaderLock(lockTimeout);
                    if (this.c_eventName == null)
                        break;
                    locker.ReleaseReaderLock();
                }
                return this.c_eventData;
            }
            finally
            {
                locker.ReleaseReaderLock();
                worker.ReleaseSyncing();
            }
        }
        public void ProcessMain()
        {
            if (this.m_eventName != null)
            {
                Func<string, Package, Package> func = this.worker.messageByMain;
                try
                {
                    locker.AcquireWriterLock(lockTimeout);
                    Package data = null;
                    if (this.m_eventName != null && func != null)
                        data = func(this.m_eventName, this.m_eventData);
                    this.m_eventData = data;
                }
                catch (Exception e)
                {
                    this.m_eventData = null;
                    throw e;
                }
                finally
                {
                    this.m_eventName = null;
                    locker.ReleaseWriterLock();
                }
            }
        }
        public void ProcessChild()
        {
            if (this.c_eventName != null)
            {
                Func<string, Package, Package> func = this.worker.messageByChild;
                try
                {
                    locker.AcquireWriterLock(lockTimeout);
                    Package data = null;
                    if (this.c_eventName != null && func != null)
                        data = func(this.c_eventName, this.c_eventData);
                    this.c_eventData = data;
                }
                catch (Exception e)
                {
                    this.c_eventData = null;
                    throw e;
                }
                finally
                {
                    this.c_eventName = null;
                    locker.ReleaseWriterLock();
                }
            }
        }
    }
    public class Package
    {
        /**data type */
        public Type type;
        /**data value */
        public object value;
        /**info */
        public object info;
        /**object id */
        public int id = -1;

        public static byte[] ToBytes(Puerts.ArrayBuffer value)
        {
            if (value != null)
            {
                var source = value.Bytes;
                var result = new byte[source.Length];
                Array.Copy(source, 0, result, 0, source.Length);
                return result;
            }
            return null;
        }
        public static Puerts.ArrayBuffer ToArrayBuffer(byte[] value)
        {
            if (value != null)
                return new Puerts.ArrayBuffer(value);
            return null;
        }
    }
    public enum Type
    {
        Unknown,
        Value,
        Object,
        Array,
        Function,
        /**ArrayBuffer类型为指针传递, 直接传递将多线程共享内存而crash */
        ArrayBuffer,
        RefObject
    }
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Unity使用线程可以提高游戏的性能和响应速度。但需要注意的是,Unity的所有API都是线程不安全的,所以在使用线程时,必须采用正确的方式来访问Unity API。 以下是在Unity使用线程的步骤: 1. 创建新的线使用C#的Thread类创建新的线程,如下所示: ``` Thread thread = new Thread(ThreadMethod); thread.Start(); ``` 其,`ThreadMethod`是新线程要执行的方法。 2. 在新线执行逻辑 在新线执行复杂的计算或其他需要耗费时间的操作。需要注意的是,在新线不能直接访问Unity API。 3. 使用线程安全的方式访问Unity API 为了避免访问Unity API时出现线程安全问题,可以使用以下方法: - 使用线程安全的类型,如ConcurrentQueue,来存储需要在主线处理的数据。 - 使用Unity的主线程调用方法,如`UnityMainThreadDispatcher.Instance().Enqueue()`方法,将需要在主线执行的代码添加到主线程的执行队列。 以下是一个使用线程的示例代码: ``` private ConcurrentQueue<float> queue = new ConcurrentQueue<float>(); private void Update() { float value; while (queue.TryDequeue(out value)) { // 在主线处理数据 Debug.Log(value); } } private void ThreadMethod() { for (float i = 0; i < 10000; i++) { queue.Enqueue(i); } } ``` 在上面的示例代码,我们使用ConcurrentQueue存储需要在主线处理的数据。然后在Update方法不断地尝试从队列取出数据并在主线处理。在新线,我们向队列添加数据。这样可以保证在主线处理数据,避免了访问Unity API时出现线程安全问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值