有一个很常见的功能,我们需要在一个子窗口中定时调用服务,然后更新控件的内容,只要窗口开着就一直会调用服务。
那么现在就来完成这个功能,首先定义一个服务:
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
运行程序,打开子窗口:
看看服务端的日志:
貌似完成了我们的需求,这个程序真的没问题吗?
第二次打开子窗口:
也没问题,但是服务端的日志就不对了(看到没问题不代表真的没问题啊):
这说明两个后台线程同时在运行,不能想当然认为子窗口关闭了,老的线程会结束(可能会觉得反正不是静态字段嘛,窗口关闭了等它回收Thread去)。
Silverlight的程序一般很少刷新页面,随着子窗口开关越来越多,线程也越来越多,并且以前的线程会一直进行服务调用。
随着子窗口开关无数次,后台有无数个线程在调用服务端,想想也是很恐怖的事情!
前端显示一点问题都没有(为什么ListBox却没有多显示?大家可以思考一下),潜在增加了服务端的压力,我们希望的是再子窗口关闭之后,后台线程可以结束。
很多人可能会这么写:
private void ChildWindow_Closed(object sender, EventArgs e)
{
if (thread != null)
{
try
{
thread.Abort();
}
catch
{
}
}
}
子窗口关闭的时候结束线程嘛,反正它会抛ThreadAbortException,不用管了。
开关几次子窗口看看:
还是一样,难道线程没结束?
想当然了!其实何必强制结束线程呢?让线程自己完成就可以了:
这样就正常了。当然,也可以使用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程序也不能啥都不释放不去管,对于线程、网络链接等资源、大量内存分配还是需要多关注。