代码改变世界

silverlight游戏设计(二)资源管理篇(上)--实现一个多任务资源下载器

2010-12-21 22:32  姜 萌@cnblogs  阅读(1569)  评论(2编辑  收藏  举报

我在silverlight游戏设计(一)主程序加载器这篇文章中写的那个加载器中其实已经包含了一个下载器。不过本篇需要的是一个在游戏运行时动态执行的下载器,这两者有什么区别呢?一方面在于前篇的那个下载器是串行下载所有文件,而对于一个正在运行的网游,因为我们对大部分资源(比如图片、脚本等)采取动态加载的策略,所以它随时都有可能去请求下载,也就是需要同时进行更多的下载任务;二是我们这个下载器与接下来资源管理服务时紧密相关,所以需要重新设计新的接口。

设计要点 

image

a.限制webclient数量,池化webclient

带宽总是有限的,我们不可能每次需要下载一个资源时都new一个webclient,最好的方式是我们可以通过配置限定最多允许多少个webclient执行下载任务,多出这个数量的下载请求先辈暂存起来,当其他下载任务结束时再进行下载。

关于这个功能的实现,我们可以先判断当前工作中的webclient数量,如果大于限制数则将任务请求存放到队列中。

代码片段-Download方法
public void Download(Uri uri, DownloadResultEventHandler callback)

        {

            
if(_currentWorkersNum + 1 > _maxConnectionInPool)

            {

                enqueue(
new InnerBuffData() { Uri = uri, Handler = callback });

                
return;

            }

            var client 
= _webClients.Detach();

            client.OpenReadCompleted 
+= onDownloadResultArrive;

            client.OpenReadAsync(uri, callback);

            increaseRefCounter();

        }

 

 

在一个任务结束时,检查当前任务队列是否有剩余任务,如果有提取出来继续下载,否则将工作中的webclient计数减1。这里要注意加锁。

代码片段 - onDownloadResultArrive
private void onDownloadResultArrive(object sender, OpenReadCompletedEventArgs args)

        {
 …… …… ……
 
lock (_lock_BuffTargets)

            {

                
if (_buffTargets.Count != 0)

                {

                    var targetInfo 
= dequeue();

                    client.OpenReadAsync(targetInfo.Uri, targetInfo.Handler);

                }

                
else

                {

                    client.OpenReadCompleted 
-= onDownloadResultArrive;

                    decreaseRefCounter();

                    _webClients.Attach(client);

                    
return;

                }

            }
        }

 

 

b.在下载结束(失败或是成功)后通过回调的方式进行通知。所以一个任务除了包含URI之外,还需要一个回调对象。这里我定义了一个委托类型:

public delegate void DownloadResultEventHandler(MemoryStream stream, DownloadStatus status);
能够将数据流和下载状态(完成/失败等)数据进行传递。
DownloadDelegater代码如下(详细见源码中的
Sopaco.Silverlight.Insfrastructure.Net.DownloadActivator
这个命名空间):
DownloadDelegater
namespace Sopaco.Silverlight.Insfrastructure.Net.DownloadActivator
{
    
public class DownloadDelegater : IDisposable
    {
        
class InnerBuffData
        {
            
public Uri Uri
            {
                
get;
                
set;
            }
            
public DownloadResultEventHandler Handler
            {
                
get;
                
set;
            }
        }

        
#region Constaints
        
private readonly static int DEFAULT_CONNECTION_IN_POOL = 5;
        
#endregion
        
#region Fields
        
/// <summary>
        
/// 池化WebClient
        
/// </summary>
        private IGenericPool<WebClient> _webClients = new GenericPool<WebClient>();
        
/// <summary>
        
/// 缓冲,当WebClient数量达到限制时将需要等待的资源Uri放到这里。
        
/// </summary>
        private Queue<InnerBuffData> _buffTargets = new Queue<InnerBuffData>();
        
/// <summary>
        
/// 最大启用的WebClient数量
        
/// </summary>
        private int _maxConnectionInPool = DEFAULT_CONNECTION_IN_POOL;
        
/// <summary>
        
/// 当前进行中的WebClient数量
        
/// </summary>
        private int _currentWorkersNum = 0;
        
#endregion

        
#region lockers
        
private object _lock_CurrentWorkersNum = new object();
        
private object _lock_BuffTargets = new object();
        
#endregion


        
#region ctors
        
public DownloadDelegater()
        {
            
        }
        
#endregion
        
#region Properties
        
public int MaxConnectionInPool
        {
            
get
            {
                
return _maxConnectionInPool;
            }
            
set
            {
                _maxConnectionInPool 
= value;
            }
        }
        
#endregion
        
public void Download(Uri uri, DownloadResultEventHandler callback)
        {
            
if(_currentWorkersNum + 1 > _maxConnectionInPool)
            {
                enqueue(
new InnerBuffData() { Uri = uri, Handler = callback });
                
return;
            }
            var client 
= _webClients.Detach();
            client.OpenReadCompleted 
+= onDownloadResultArrive;
            client.OpenReadAsync(uri, callback);
            increaseRefCounter();
        }

        
#region IDisposable Members

        
public void Dispose()
        {
            disposeWebclientPool();
            _webClients.Dispose();
        }

        
#endregion
        
#region Private Helper Methods
        
private void disposeWebclientPool()
        {
            _webClients.Compact(
0);
        }
        
private void onDownloadResultArrive(object sender, OpenReadCompletedEventArgs args)
        {
            var callback 
= args.UserState as DownloadResultEventHandler;
            var client 
= sender as WebClient;
            
if(args.Result != null && args.Error == null && !args.Cancelled)
            {
                var memStream 
= new MemoryStream();
                args.Result.CopyTo(memStream);
                memStream.ResetPosition();
                callback(memStream, DownloadStatus.Completed);
            }
            
else
            {
                
if(args.Cancelled)
                {
                    callback(
null, DownloadStatus.Canceled);
                }
                
else
                {
                    callback(
null, DownloadStatus.Error);
                }
            }
            
lock (_lock_BuffTargets)
            {
                
if (_buffTargets.Count != 0)
                {
                    var targetInfo 
= dequeue();
                    client.OpenReadAsync(targetInfo.Uri, targetInfo.Handler);
                }
                
else
                {
                    client.OpenReadCompleted 
-= onDownloadResultArrive;
                    decreaseRefCounter();
                    _webClients.Attach(client);
                    
return;
                }
            }
        }
        
private void increaseRefCounter()
        {
            
lock (_lock_CurrentWorkersNum)
            {
                _currentWorkersNum
++;
            }
        }
        
private void decreaseRefCounter()
        {
            
lock (_lock_CurrentWorkersNum)
            {
                _currentWorkersNum
--;
            }
        }
        
private void enqueue(InnerBuffData data)
        {
            
lock(_lock_BuffTargets)
            {
                _buffTargets.Enqueue(data);
            }
        }
        
private InnerBuffData dequeue()
        {
            
lock(_lock_BuffTargets)
            {
                
return _buffTargets.Dequeue();
            }
        }
        
#endregion
    }
}

完整源码下载