在Silverlight程序中使用Thread一个很容易被忽略的问题

有一个很常见的功能,我们需要在一个子窗口中定时调用服务,然后更新控件的内容,只要窗口开着就一直会调用服务。

那么现在就来完成这个功能,首先定义一个服务:

    public class Service1 : IService1
    {
        public string DoWork(string name)
        {
            File.AppendAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log.txt"), string.Format("时间:{0} 线程:{1}" + Environment.NewLine, DateTime.Now, name));
            return "OK";
        }
    }

这个服务在返回数据之前会写一个本地文本日志,写入时间和传入的参数。

然后添加一个子窗口,并在页面上放置一个按钮打开这个窗口:

ChildWindow1 c = new ChildWindow1();
            c.Show();

子窗口的代码如下:

    public partial class ChildWindow1 : ChildWindow
    {
        private Thread thread;
        private static int threadNum = 0;
        private ServiceReference1.Service1Client s = new ServiceReference1.Service1Client();

        public ChildWindow1()
        {
            InitializeComponent();

            s.DoWorkCompleted += new EventHandler<ServiceReference1.DoWorkCompletedEventArgs>(s_DoWorkCompleted);
        }

        private void s_DoWorkCompleted(object sender, ServiceReference1.DoWorkCompletedEventArgs e)
        {
            Dispatcher.BeginInvoke(() => Message.Items.Insert(0, string.Format("服务返回:{0} 控件:{1} 时间:{2} 线程:{3}",
               e.Result,
               Message.GetHashCode(),
               DateTime.Now,
               e.UserState.ToString())));
        }

        private void ChildWindow_Loaded(object sender, RoutedEventArgs e)
        {
            Interlocked.Increment(ref threadNum);

            thread = new Thread(state =>
            {
                var threadID = (int)state;
                while (true)
                {
                    s.DoWorkAsync(threadID.ToString(), threadID);
                    Thread.Sleep(1000);
                }
            });
            thread.Start(threadNum);
        }

这个代码很简单:

1、我们有一个静态变量保存了总共的线程数

2、每次窗口打开+1

3、每次窗口打开的时候初始化一个线程,这个线程的作用就是不断调用服务,然后休眠1秒,调用的时候传入的参数就是当前线程编号(从1开始)

4、服务调用完成之后,我们会更新页面上的ListBox,添加一条数据,内容包括服务返回值、Message控件的标识、当前时间和后台线程的编号

好了,功能完成了,运行程序,我们的期望是在子窗口打开后:

1、子窗口的ListBox每一秒都会插入一行记录

2、服务端每一秒都会往文本文件写入一行记录

3、两者的线程编号保持一致,并且每次打开子窗口线程编号都会+1

 

运行程序,打开子窗口:

image

看看服务端的日志:

image

貌似完成了我们的需求,这个程序真的没问题吗?

第二次打开子窗口:

image

也没问题,但是服务端的日志就不对了(看到没问题不代表真的没问题啊):

image

这说明两个后台线程同时在运行,不能想当然认为子窗口关闭了,老的线程会结束(可能会觉得反正不是静态字段嘛,窗口关闭了等它回收Thread去)。

image

image

Silverlight的程序一般很少刷新页面,随着子窗口开关越来越多,线程也越来越多,并且以前的线程会一直进行服务调用。

随着子窗口开关无数次,后台有无数个线程在调用服务端,想想也是很恐怖的事情!

前端显示一点问题都没有(为什么ListBox却没有多显示?大家可以思考一下),潜在增加了服务端的压力,我们希望的是再子窗口关闭之后,后台线程可以结束。

很多人可能会这么写:

   private void ChildWindow_Closed(object sender, EventArgs e)
        {
            if (thread != null)
            {
                try
                {
                    thread.Abort();
                }
                catch
                {
                }
            }
        }

子窗口关闭的时候结束线程嘛,反正它会抛ThreadAbortException,不用管了。

开关几次子窗口看看:

image

还是一样,难道线程没结束?

image

想当然了!其实何必强制结束线程呢?让线程自己完成就可以了:

image

image

这样就正常了。当然,也可以使用BackgroundWorker来实现这个功能:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Threading;
using System.ComponentModel;

namespace SilverlightApplication2
{
    public partial class ChildWindow1 : ChildWindow
    {
        private static int threadNum = 0;
        private ServiceReference1.Service1Client s = new ServiceReference1.Service1Client();
        private BackgroundWorker bw = new BackgroundWorker();

        public ChildWindow1()
        {
            InitializeComponent();

            s.DoWorkCompleted += new EventHandler<ServiceReference1.DoWorkCompletedEventArgs>(s_DoWorkCompleted);
            bw.WorkerSupportsCancellation = true;
            bw.WorkerReportsProgress = true;
            bw.DoWork += new DoWorkEventHandler(bw_DoWork);
            bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
        }

        private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Message.Items.Insert(0, string.Format("服务器返回:{0} 控件:{1} 时间:{2} 线程:{3}",
                e.UserState,
                Message.GetHashCode(),
                DateTime.Now,
                e.UserState.ToString()));
        }

        private void s_DoWorkCompleted(object sender, ServiceReference1.DoWorkCompletedEventArgs e)
        {
            bw.ReportProgress(0, e.Result);
        }

        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            while (!bw.CancellationPending)
            {
                var threadID = (int)e.Argument;
                s.DoWorkAsync(threadID.ToString(), threadID);
                Thread.Sleep(1000);
            }
        }

        private void ChildWindow_Loaded(object sender, RoutedEventArgs e)
        {
            Interlocked.Increment(ref threadNum);
            bw.RunWorkerAsync(threadNum);
        }

        private void ChildWindow_Closed(object sender, EventArgs e)
        {
            bw.CancelAsync();
        }
    }
}

当然,本文标题虽然说Silverlight,对于Winform也是一样的,只不过Silverlight的Thread.Abort()不奏效。总之两点,一,有的时候不能太想当然和凭经验,只有实际验证了才知道结果;二,写.NET程序也不能啥都不释放不去管,对于线程、网络链接等资源、大量内存分配还是需要多关注。

posted @ 2011-11-09 12:43 lovecindywang 阅读(...) 评论(...) 编辑 收藏