我是Unity3d C#的新手,但不是编程(Java)。我尝试使用Threads,到目前为止它已经非常成功,因为它很像Java。但是我有代码锁定而且我试图找出原因。 DownloadStatus只是一个枚举。
从ThreadPoolCallback调用以下代码:
public static Dictionary<int, int> downloading = new Dictionary<int, int>();
...
//Look on the download list
while (true) {
foreach (int id in downloading.Keys) {
lock(downloading){
Debug.Log("Thread " + threadIndex + " checking id: "+id);
if(downloading [id]==(int)DownloadStatus.WAITING){
//This file is waiting
downloading [id] = (int)DownloadStatus.DOWLOADING;
Debug.Log ("Thread " + threadIndex + " is downloading " + id);
//TODO: Actual downloading
//Mark as done
downloading [id] = (int)DownloadStatus.DONE;
Debug.Log ("Thread " + threadIndex + " is done downloading " + id);
}else if(downloading [id]==(int)DownloadStatus.DOWLOADING){
Debug.Log ("Thread " + threadIndex + " ignoring "+id+" since is already downloading!");
}else if(downloading [id]==(int)DownloadStatus.DONE){
Debug.Log ("Thread " + threadIndex + " ignoring "+id+" since is already downloaded!");
}
}
}
//If you made it here, there's nothing to process
Debug.Log ("Thread " + threadIndex + " closed!");
_doneEvent.Set();
break;
}
如果我注释我更改字典值的行(即下载[id] =(int)DownloadStatus.DOWLOADING;)我可以看到线程通过字典中的所有值。当我没有线程停在那里时。此状态的唯一目的是阻止其他线程尝试相同的下载。
也许只是错误的方法来实现这一点。
有什么想法吗?
更新
填写下载代码:
Retriever.downloading.Add(id,(int)DownloadStatus.WAITING);
其中Retriever是Thread类的名称。
根据KeithS的建议,代码改为:
public static Dictionary<int, int> downloading = new Dictionary<int, int>();
private readonly object syncObj = new object();
...
//Look on the download list
foreach (int id in downloading.Keys) {
lock(syncObj){
Debug.Log("Thread " + threadIndex + " checking id: "+id);
if(downloading [id]==(int)DownloadStatus.WAITING){
//This file is waiting
downloading [id] = (int)DownloadStatus.DOWLOADING;
Debug.Log ("Thread " + threadIndex + " is downloading " + id);
//TODO: Actual downloading
//Mark as done
downloading [id] = (int)DownloadStatus.DONE;
Debug.Log ("Thread " + threadIndex + " is done downloading " + id);
}else if(downloading [id]==(int)DownloadStatus.DOWLOADING){
Debug.Log ("Thread " + threadIndex + " ignoring "+id+" since is already downloading!");
}else if(downloading [id]==(int)DownloadStatus.DONE){
Debug.Log ("Thread " + threadIndex + " ignoring "+id+" since is already downloaded!");
}
}
}
//If you made it here, there's nothing to process
Debug.Log ("Thread " + threadIndex + " closed!");
_doneEvent.Set();
break;
}
这是输出(只有2个线程用于测试目的):
914 files to update!
Thread 1 started...
Thread 2 started...
Thread 1 checking id: 2
Thread 1 is downloading 2
Thread 1 is done downloading 2
Thread 1 closed!
并且基本上永远呆在那里。我之前的行为也是如此。
答案 0 :(得分:2)
Unity中最常见的方法是使用coroutines。虽然它们不是线程,但你会得到一个类似异步行为的线程(例如Threading in Unity,更多信息)。
在AltDevBlog上有一篇非常有趣的文章Unity3D coroutines in detail,但它似乎被删除了。很高兴有互联网档案回归机器:
https://web.archive.org/web/20140719082851/http://www.altdev.co/2011/07/07/unity3d-coroutines-in-detail/
可以使用WWW课程轻松完成下载。把它放在一起:
public void Download (string string url)
{
StartCoroutine (Download_Coroutine (url));
}
IEnumerator Download_Coroutine (string url)
{
using (WWW www = WWW.LoadFromCacheOrDownload (bundleUrl, CurrentVersion))
{
while (!www.isDone) {
yield return null;
// optionally report www.progress to callback
}
if (string.IsNullOrEmpty (www.error)) {
// s. doc for more options to get the content
AssetBundle bundle = www.assetBundle;
if (bundle != null) {
// do something
}
}
}
}
答案 1 :(得分:1)
首先,避免在代码中有另一个用途的对象上使用lock
。最佳做法是lock
专门为此目的创建的对象实例:
private readonly object syncObj = new object();
...
lock(syncObj)
{
...
其次,while(true)
语句是当前实现的冗余。每个工作人员将完成整个下载集合,直到找到“等待”下载,它将处理该下载,然后继续下一个记录。一旦每个工作者到达集合的末尾,它将执行循环底部的代码,这将突破它而不会循环一次。
现在,基于此代码(以及填充downloading
的看不见的代码),您可能对此集合有错误的抽象。多线程代码的目的是并行分割和征服一堆下载。那么,为什么不将它们加载为Queue<Tuple<int,int>>
(甚至更好,ConcurrentQueue<Tuple<int,int>>
),并让你的工作线程从队列中拉出项目,直到没有更多?这样,它就是集合,而不是工人或工作项,确保只有一个工人拥有特定的工作项。
worker将从队列中获取该项(对此目的使用的“TryTake()”方法是同步的,因此一次只能有一个worker可以排队),下载它所代表的文件,然后完成后,它可以将项目放在ConcurrentDictionary中,表明它已完成,然后返回队列以获取更多信息,直到不再存在为止。如果下载时出现错误,工作人员应该抓住它并将项目放回队列中,以便其他工作人员可以尝试,或者将其放入错误集合中(可能两者都是基于下载或性质的重试次数)错误)。