您的位置:首页 > 移动开发 > Unity3D

Unity多线程(Thread)和主线程(MainThread)交互使用类——Loom工具分享

2016-05-16 20:13 633 查看


问题

每次Unity3D编辑器打开时, 连接服务器都会有一定几率失败, 需要反复关闭再打开编辑器3~4次后, 才能正常接收到封包

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy


探索

我们的网络库基于C#的Begin/End系的异步Socket, 这种socket更接近C++的asio模型, 撸起来特爽.

1. 根据经验, 这个诡异问题多半跟多线程有关系. 复查代码, 无效.

2. 找友人更换网络库, 换阻塞Socket实现和SocketAsyncEventArgs这种实现都试过, 仍然无法解决问题.

3. 接下来还是对Begin/End系的网络库进行日志追踪. 发现, 发送会总是成功, 连接成功和接收封包有一定几率会断掉

我们并没有单独开线程来处理, 而是利用底层异步通知, 然后有线程安全队列切换到主线程进行投递. 因此底层的线程正常性是整个问题的焦点

由于一直无法找到原因, 这个问题搁置了

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy


解决方案

直到有一个偶然的机会, 取过同事代码后. 突然发现第一次打开Unity3D编辑器可以直接登录. 但之后又不行. 同事提醒, 会不会是优先度问题.

马上打开Edit->Project Settings->Script Execution Orders. 提高了网络组建优先度





测试, 通过, 问题解决

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy


总结

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy

一直怀疑这个问题跟Mono版本过老有关系, 但由于5.2版本到年底才更新, 之前只能自己啃bug.

在这个问题发生后解决前, 我们还有一个相关见闻: 我们将网络部分比较稳定的代码拆分放到dll中, 通过Unity3D的机制进行加载

结果, 网络无法初始化. 估计也是跟这个问题有关系

总之, 有类似问题时, 可以试用脚本执行顺序大法进行尝试

熟悉Unity的developer都知道在Unity中的线程不能使用Unity的对象,但可以使用Unity的值类型变量,如Vector3等。这样就使得线程在Unity中显的很鸡肋和蹩脚,因为很多函数很都是UnityEngine类或函数的调用的,对于哪些是可以在多线程使用,风雨冲进行了如下总结:

0. 变量(都能指向相同的内存地址)都是共享的

1. 不是UnityEngine的API能在分线程运行

2. UnityEngine定义的基本结构(int,float,Struct定义的数据类型)可以在分线程计算,如 Vector3(Struct)可以 , 但Texture2d(class,根父类为Object)不可以。

3. UnityEngine定义的基本类型的函数可以在分线程运行,如

int i = 99;

print (i.ToString());

Vector3 x = new Vector3(0,0,9);

x.Normalize();

类的函数不能在分线程运行

obj.name

实际是get_name函数,分线程报错误:get_name can only be called from the main thread.

Texture2D tt = new Texture2D(10,10);

实际会调用UnityEngine里的Internal_Create,分线程报错误:Internal_Create can only be called from the main thread.

其他transform.position,Texture.Apply()等等都不能在分线程里运行。

结论: 分线程可以做 基本类型的计算, 以及非Unity(包括.Net及SDK)的API。

D.S.Qiu觉得Unity做了这个限制,主要是Unity的函数执行机制是帧序列调用,甚至连Unity的协程Coroutine的执行机制都是确定的,如果可以使用多线程访问UnityEngine的对象和api就得考虑同步问题了,也就是说Unity其实根本没有多线程的机制,协程只是达到一个延时或者是当指定条件满足是才继续执行的机制。

我们的项目目前还有没有比较耗时的计算,所以还没有看到Thread的使用。本来一直没有太考虑着方面的事情,直到在UnityGems.com看到Loom这个类,叹为观止呀。直接贴出人家的介绍(没必要翻译了

):


Threads on a Loom

Our class is called Loom. Loom lets you easily run code on another thread and have that other thread run code on the main game thread when it needs to.

There are only two functions to worry about:

RunAsync(Action) which runs a set of statements on another thread

QueueOnMainThread(Action, [optional] float time) - which runs a set of statements on the main thread (with an optional delay).

You access Loom using Loom.Current - it deals with creating an invisible game object to interact with the games main thread.

我们只需要关系两个函数:RunAsync(Action)和QueueOnMainThread(Action, [optional] float time) 就可以轻松实现一个函数的两段代码在C#线程和Unity的主线程中交叉运行。原理也很简单:用线程池去运行RunAsync(Action)的函数,在Update中运行QueueOnMainThread(Acition, [optional] float time)传入的函数。

直接贴出源码,供拜读:

C#代码


using UnityEngine;

using System.Collections;

using System.Collections.Generic;

using System;

using System.Threading;

using System.Linq;

public class Loom : MonoBehaviour

{

public static int maxThreads = 8;

static int numThreads;

private static Loom _current;

private int _count;

public static Loom Current

{

get

{

Initialize();

return _current;

}

}

void Awake()

{

_current = this;

initialized = true;

}

static bool initialized;

static void Initialize()

{

if (!initialized)

{

if(!Application.isPlaying)

return;

initialized = true;

var g = new GameObject("Loom");

_current = g.AddComponent<Loom>();

}

}

private List<Action> _actions = new List<Action>();

public struct DelayedQueueItem

{

public float time;

public Action action;

}

private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();

List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();

public static void QueueOnMainThread(Action action)

{

QueueOnMainThread( action, 0f);

}

public static void QueueOnMainThread(Action action, float time)

{

if(time != 0)

{

lock(Current._delayed)

{

Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action});

}

}

else

{

lock (Current._actions)

{

Current._actions.Add(action);

}

}

}

public static Thread RunAsync(Action a)

{

Initialize();

while(numThreads >= maxThreads)

{

Thread.Sleep(1);

}

Interlocked.Increment(ref numThreads);

ThreadPool.QueueUserWorkItem(RunAction, a);

return null;

}

private static void RunAction(object action)

{

try

{

((Action)action)();

}

catch

{

}

finally

{

Interlocked.Decrement(ref numThreads);

}

}

void OnDisable()

{

if (_current == this)

{

_current = null;

}

}

// Use this for initialization

void Start()

{

}

List<Action> _currentActions = new List<Action>();

// Update is called once per frame

void Update()

{

lock (_actions)

{

_currentActions.Clear();

_currentActions.AddRange(_actions);

_actions.Clear();

}

foreach(var a in _currentActions)

{

a();

}

lock(_delayed)

{

_currentDelayed.Clear();

_currentDelayed.AddRange(_delayed.Where(d=>d.time <= Time.time));

foreach(var item in _currentDelayed)

_delayed.Remove(item);

}

foreach(var delayed in _currentDelayed)

{

delayed.action();

}

}

}

怎么实现一个函数内使用多线程计算又保持函数体内代码的顺序执行,印象中使用多线程就是要摆脱代码块的顺序执行,但这里是把原本一个函数分拆成为两部分:一部分在C#线程中使用,另一部还是得在Unity的MainThread中使用,怎么解决呢,还得看例子:

C#代码


//Scale a mesh on a second thread

void ScaleMesh(Mesh mesh, float scale)

{

//Get the vertices of a mesh

var vertices = mesh.vertices;

//Run the action on a new thread

Loom.RunAsync(()=>{

//Loop through the vertices

for(var i = 0; i < vertices.Length; i++)

{

//Scale the vertex

vertices[i] = vertices[i] * scale;

}

//Run some code on the main thread

//to update the mesh

Loom.QueueOnMainThread(()=>{

//Set the vertices

mesh.vertices = vertices;

//Recalculate the bounds

mesh.RecalculateBounds();

});

});

}

这个例子是对Mesh的顶点进行放缩,同时也是一个使用闭包(closure)和lambda表达式的一个很好例子。看完例子,是不是很有把项目中一些耗时的函数给拆分出来,D.S.Qiu就想用这个方法来改进下NGUI的底层机制(看下性能不能改进)。

小结:

D.S.Qiu在编程技术掌握还是一个菜鸟,Thread还是停留在实现Runable接口或继承Thread的一个水平上,对多线程编程的认识还只是九牛一毛。本来我以为Loom的实现会比较复杂,当我发现只有100多行的代码是大为惊叹,这也得益于现在语言的改进,至少从语言使用的便利性上还是有很大的进步的。

有了Loom这个工具类,在很多涉及UnityEngine对象的耗时计算还是可以得到一个解决方法的:

如在场景中用A*算法进行大量的数据计算

变形网格中操作大量的顶点

持续的要运行上传数据到服务器

二维码识别等图像处理

Loom简单而又巧妙,佩服Loom的作者。

如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。

转载请在文首注明出处:/article/3759467.html

更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)

参考:

①UnityGems:http://unitygems.com/threads/

Xiaoke's Bloghttp://blog.1vr.cn/?p=624

③风宇冲:http://blog.sina.com.cn/s/blog_471132920101hh5d.html

Loom.zip (2.5 KB)
下载次数: 54
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: