C#Unity3d中的线程和队列

本文关键字:队列 线程 C#Unity3d | 更新日期: 2023-09-27 18:27:33

我是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;
        }

如果我对更改字典值的行进行注释(即downloading[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!

并且基本上永远留在那里。和我以前的行为一样。

C#Unity3d中的线程和队列

Unity中最常见的方法是使用协程。虽然它们不是线程,但你会得到一种几乎像线程一样的异步行为(例如,在Unity中线程)。

AltDevBlog上有一篇非常有趣的文章Unity3D协同程序详细介绍了这一点,但它似乎被删除了。很高兴拥有互联网档案折返机:
https://web.archive.org/web/20140719082851/http://www.altdev.co/2011/07/07/unity3d-详细协同程序/

使用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
            }
        }
    }
}

首先,避免在代码中有其他用途的对象上使用lock。最佳实践是lock一个专门为以下目的创建的对象实例:

private readonly object syncObj = new object();
...
lock(syncObj)
{
 ...

其次,while(true)语句与当前实现的一样是多余的。每个工作者都将浏览整个下载集合,直到找到一个"等待"的下载,它将处理该下载,然后转到下一个记录。一旦每个工作者到达集合的末尾,它将在循环的底部执行代码,这将在不循环一次的情况下中断代码。

现在,仅基于这段代码(以及用于填充downloading的看不见的代码),您可能对这个集合有错误的抽象。多线程代码的目的是并行地划分和征服一堆下载。那么,为什么不将它们加载为Queue<Tuple<int,int>>(或者更好的是,ConcurrentQueue<Tuple<int,int>>),并让您的工作线程从队列中提取项目,直到不再有项目为止?通过这种方式,它是集合,而不是工作者或工作项,确保只有一个工作者拥有特定的工作项。

工作进程将从队列中获取该项(对用于此目的的"TryTake()"方法的访问是同步的,因此一次只能有一个工作进程退出队列),下载它所代表的文件,然后完成后,它可以将该项放在ConcurrentDictionary中,指示它已完成,然后返回队列获取更多信息,直到没有更多信息为止。如果下载过程中出现错误,工作人员应该抓住它,然后将项目放回队列中,以便另一个工作人员可以尝试,或者将它放在错误集合中(根据下载的重试次数或错误的性质,可能两者都有)。