从Socket数据处理线程想到的普通Winform数据显示的应用

在前面介绍过Socket编程的文章中,有一篇是《Socket开发探秘--基类及公共类的定义》,其中介绍了一个独立线程处理类,专门在一个独立的线程中处理Socket的数据包的。摘录前面的内容介绍一下:

5、ThreadHandler,数据独立线程处理类

对每个不同类型的数据(不同的协议类型),可以用独立的线程进行处理,这里封装了一个基类,用于进行数据独立线程的处理 

 
上面的工作原理是这样的,每次收到数据后,系统把数据扔给独立线程处理类,处理类放到一个队列Queue的列表中,每次从中弹出一个来处理,根据不同的协议头,分派到不同的线程来处理,这样可以提高响应速度,防止线程之间的阻塞,能够充分利用系统的资源。

 其实我们还可以把这个思想应用到日常的Winform开发中,有时候我们可能在处理一些比较费时的操作,可能是需要做一部分显示一部分,类似日常生活中的项目周报、月周报的场景,因为不可能等一个几年的项目完成后,你才告诉老板你的工作情况吧。

 借鉴Socket的数据处理方式,我在Winform程序中运用了这种数据处理方式,如我在采集赶集网的数据的时候,可以把采集到的部分数据扔给系统中的数据独立处理线程,让他们爱怎么显示就怎么显示,程序不中断,继续乐此不彼的去采集内容去,然后继续这样做(每采集一部分仍出去一部分),直到采集完毕。

代码
    public class ThreadHandler<T>
    {
        
/// <summary>
        
/// 处理数据线程
        
/// </summary>
        Thread _Handlehread = null;
        
private string _ThreadName = "";
        
private Fifo<T> _DataFifo = new Fifo<T>();

        
/// <summary>
        
/// 线程名字
        
/// </summary>
        public string ThreadName
        {
            
get { return _ThreadName; }
            
set { _ThreadName = value; }
        }

        
/// <summary>
        
/// 接收处理数据
        
/// </summary>
        
/// <param name="data"></param>
        public virtual void AppendData(T data)
        {
            
if (data != null)
                _DataFifo.Append(data);
        }

        
/// <summary>
        
/// 数据处理
        
/// </summary>
        protected virtual void DataThreadHandle()
        {
            
try
            {
                
while (true)
                {
                    T data 
= _DataFifo.Pop();
                    DataHandle(data);
                }
            }
            
catch(Exception ex)
            {
                LogHelper.Error(ex);
            }
        }

        
/// <summary>
        
/// 数据处理
        
/// </summary>
        
/// <param name="data"></param>
        public virtual void DataHandle(T data)
        {
        }

        
/// <summary>
        
/// 开始数据处理线程
        
/// </summary>
        public virtual void StartHandleThread()
        {
            
if (_Handlehread == null)
            {
                _Handlehread 
= new Thread(new ThreadStart(DataThreadHandle));
                _Handlehread.IsBackground 
= true;
                _Handlehread.Start();
            }
            LogHelper.Info(
string.Format("[ThreadHandler] 线程->{0}启动。。。。。。", _ThreadName));
        }

 

上面的是独立线程处理的基类,下面我们用一个子类继承他,方便代码逻辑的剥离封装:

在下面的代码中,我根据不同的Table表内容类型,放到不同的函数中进行处理,以便实现不同的显示方式。 

 代码

    public class TestDataHandleThread : ThreadHandler<PreData>
    {
        
public TestDataHandleThread()
        {
            
base.ThreadName = "测试数据操作处理线程";
        }

        
public override void DataHandle(PreData data)
        {
            
try
            {
                
if (data.Key == KeyType.PostAticle)
                {
                    
if (!string.IsNullOrEmpty(data.Content.TableName))
                    {
                        ThreadPool.QueueUserWorkItem(
new WaitCallback(Portal.gc.MainDialog.DisplayForm), data.Content);
                    }
                }
                
else if (data.Key == KeyType.ContactInfo)
                {
                    
if (!string.IsNullOrEmpty(data.Content.TableName))
                    {
                        ThreadPool.QueueUserWorkItem(
new WaitCallback(Portal.gc.MainDialog.DisplayContactForm), data.Content);
                    }
                }
            }
            
catch (Exception ex)
            {
                LogHelper.Error(
"[TestDataHandleThread] 测试数据操作处理线程异常:{0}" + ex.ToString());
            }
        }
    }

 

下面代码是表的不同类型的枚举类和预处理数据格式定义。 

代码
    public enum KeyType{PostAticle, ContactInfo};

    
/// <summary>
    
/// 预处理的数据
    
/// </summary>
    public class PreData
    {
        
private KeyType key;
        
private DataTable content;

        
public KeyType Key
        {
            
get { return key; }
            
set { key = value; }
        }

        
public DataTable Content
        {
            
get { return content; }
            
set { content = value; }
        }

        
public PreData(KeyType key, DataTable data)
        {
            
this.key = key;
            
this.content = data;
        }
    }

 

 在实际的赶集网采集程序中,我需要每采集一个链接的内容后,就处理并显示,因此示例代码如下所示:

代码
        /// <summary>
        
/// 获取网站发布内容,并添加到线程进行处理
        
/// </summary>
        
/// <param name="itemDict"></param>
        
/// <param name="regexDict"></param>
        private void GetContent(Dictionary<stringstring> itemDict)
        {
            
foreach (string key in itemDict.Keys)
            {
                DataTable dt 
= new DataTable(key);

                
//标题解析,省略N行代码
                
//内容解析,省略N+N行代码

                
//添加到线程进行处理
                Portal.gc.MainDialog.AddData(new PreData(KeyType.PostAticle, dt));
            }
        }

 

代码
        /// <summary>
        
/// 添加消息数据,根据不同的消息类型分派到不同的线程处理
        
/// </summary>
        
/// <param name="data">消息数据</param>
        public void AddData(PreData data)
        {
            _testDataThread.AppendData(data);
        }

        
/// <summary>
        
/// 采用多线程方式显示内容数据
        
/// </summary>
        
/// <param name="data"></param>
        public void DisplayForm(object table)
        {
            DataTable data 
= table as DataTable;
            FrmContent content 
= FindDocument(data.TableName) as FrmContent;
            
if (content == null)
            {
                content 
= new FrmContent();
                content.TabText 
= data.TableName;
                content.Text 
= data.TableName;
            }           

            
this.Invoke(new MethodInvoker(delegate()
            {
                content.BindData(data, data.TableName);
                content.Show(
this.dockPanel);
            }));            
        }

 

好了,思路是思路,程序是程序,两者结合就是实践的证明,采集大量的网站连接的时候,在也不会出现主界面停顿或者假死的情况了。下面是我闲暇时间的练笔之作, 贴图以证方案之可行。

 

在采集的时候,整个程序再也不会出现假死的情况,你还可以去处理其他工作的。另外,由于涉及了线程的处理工作,你还需要定时检测处理线程,如果线程有问题,还需要重启线程就可以了,这部分是属于线程检查优化的部分,不再介绍。

posted on 2010-02-05 23:15  伍华聪  阅读(5074)  评论(8编辑  收藏  举报

导航