Unity跨线程操作的问题

发表于2015-08-16
评论0 1.3k浏览
       Unity是不允许后台线程操作UI层资源的,当然这里也包括gameobject相关的东西,连texture的生成都不允许后台线程操作。这一点,其实无可厚非,为了安全起见,其实很多都这么干的,除了超级信任程序员的C++,C++语言的设计基于对人的信任,觉得程序员这么写都是有理由的,当然,事实证明,永远别和上帝比赛,因为上帝肯定能给你整出更白痴更不值得信任的用户。

       所以,事实也证明了,C++程序容易coredump,这个都是得益于信任了不该信任的程序员。因为程序员其实大部分时候头脑就是浆糊,他自己都不清楚自己为啥写下这几百行代码,所以,某一天他回头看的时候会很费解当时为何如此白痴,然后卡卡全删掉。有了C++的前车之鉴,后来新生代的java和C#就来不信任用户了,觉得用户全傻逼,整天干不靠谱的事情,于是一堆限制,如C#中switch下的case必须配合break,否则case下面只能一行不写。如goto语句绝对不准竟然跑到循环里面去,等等,其中UI不准跨线程操作也是其中一点。当然微软没做那么绝,你还可以通过代码让他相信你,不过,不建议你这么干,因为你真的不值得信任的。这一点,unity就很好,绝对不信任你这个坑爹货。

       现在的问题是,我们真的需要跨线程操作UI资源,如根据通信得到的数据决定某个物体变大变小还是旋转的等等,很正常的需求,如从服务器拿到mesh数据动态生成出一个物体,又或者从网络某个旮旯处拿到一张图的流以及着色器的文本,生成出一个材质球,然后交给某个物体使用。这些都是正常不过的功能需求。但不能跨线程咋办,通信总不能发放主线程来同步通信吧,除非你是天坑,否则,你绝对不会这么干。


       本文就提供一个很是简单的脚本,解决这个问题。然后你就可以类似winform里面的this.invoke()来实现夸线程操作了。


上代码:
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
 
/* Copyright (c) 2015 3shine.com
* Anthor:penguin_ku(十月)
* DateTime:2015/5/3 22:35:41
* FileName:MainTaskProcessor
* MachineName: win8.1-04020905
* Version:V1.0.0.0
*
* Function:
* 1、
* 2、
* 3、
* 4、
*
* Tip:
* 1、
* 2、
* 3、
* 4、
*
* Modify:
* DateTime:
* Remark:
*/
using Assets.Core.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
/// <summary>
/// 主线程任务处理器
/// </summary>
public class MainTaskProcessor : MonoBehaviour
{
#region 私有变量
private static System.Object m_oneLoopQueueLock = new object();
private static List<TaskUnit> m_oneLoopQueues = new List<TaskUnit>();
#endregion
#region 公开方法
/// <summary>
/// 追加一次性任务
/// </summary>
/// <param name="p_action"></param>
public static void AppendOneAction(TaskUnit p_action)
{
lock (m_oneLoopQueueLock)
{
m_oneLoopQueues.Add(p_action);
}
}
#endregion
#region 生命周期
private void Start()
{
gameObject.name = "MainTaskProcessor";
}
private void Update()
{
for (int i = 0; i < m_oneLoopQueues.Count; i++)
{
var item = m_oneLoopQueues[i];
item.CurrWait += Time.deltaTime;
if (item.CurrWait >= item.Interval)
{
item.Action();
item.CurrWait = 0;
item.CurrLoopTimes++;
if (item.LoopTimes != -1 && item.CurrLoopTimes >= item.LoopTimes)
{
m_oneLoopQueues.RemoveAt(i);
i--;
}
}
}
}
#endregion
}
 


其中有一个结构体
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
 
/* Copyright (c) 2015 3shine.com
* Anthor:penguin_ku(十月)
* DateTime:2015/8/11 10:04:24
* FileName:ActionUnit
* Version:V1.0.0.0
*
* Function:
* 1、
* 2、
* 3、
* 4、
*
* Tip:
* 1、
* 2、
* 3、
* 4、
*
* Modify:
* DateTime:
* Remark:
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// 执行单元
/// </summary>
public class TaskUnit
{
#region 公开属性
/// <summary>
/// 设置或获取需要执行的操作
/// </summary>
public Action Action { set; get; }
/// <summary>
/// 设置或获取等待间隔
/// </summary>
public float Interval { set; get; }
/// <summary>
/// 设置或获取需要循环执行次数
/// </summary>
public int LoopTimes { set; get; }
/// <summary>
/// 设置或获取当前等待时间
/// </summary>
public float CurrWait { set; get; }
/// <summary>
/// 设置或获取当前循环次数
/// </summary>
public int CurrLoopTimes { set; get; }
#endregion
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
/// <param name="p_action"></param>
/// <param name="p_interval"></param>
/// <param name="p_loopTimes"></param>
public TaskUnit(Action p_action, float p_interval = 0, int p_loopTimes = 1)
{
Action = p_action;
Interval = p_interval;
LoopTimes = p_loopTimes;
CurrWait = 0;
CurrLoopTimes = 0;
}
#endregion
}
 


       于是你就可以这么使用了,在场景里面新增一个物体,把那个脚本拖上去。如果你涉及多个场景切换的话,记得脚本里面增加一句标记下别销毁。
       然后在需要跨线程的地方就这样用:
1
2
3
4
 
MainTaskProcessor.AppendOneAction(new TaskUnit(() =>
{
/*你的代码*/
}));
 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引