• 在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程序也不能啥都不释放不去管,对于线程、网络链接等资源、大量内存分配还是需要多关注。

    欢迎大家阅读我的极客时间专栏《Java业务开发常见错误100例》【全面避坑+最佳实践=健壮代码】
  • 相关阅读:
    八张图读懂未来“互联网+”的六大趋势
    跑一段代码遍历所有汉字
    PHP业务逻辑层和数据访问层设计
    漫谈社区PHP 业务开发
    以Apache服务器、php语言为例 详解动态网站的访问过程
    sublime text
    《产品经理的20堂必修课》
    检测文件是否有bom头
    利用开源框架Volley来下载文本和图片。
    往SD卡中写文件的方法。
  • 原文地址:https://www.cnblogs.com/lovecindywang/p/2242337.html
Copyright © 2020-2023  润新知