打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
.net多线程程序的一些技巧与心得 — Windows Live

.net多线程程序的一些技巧与心得

前两天跟某人解释回调的时候翻到一篇以前写的东东,还没发过,拿出来发一下
.net多线程程序的一些技巧与心得
在B/S开发中我们可能很少需要用到多线程,一方面,在同一个会话中需要同时执行的操作并不多,另一方面,因为浏览器为我们做了一些工作。服务器进行长时间运算的时候浏览器会等待,并有一些友好的提示。而不是挂起在那。(如果你觉得这些提示还不能令人满意,一些客户端技术如AJAX能达到一些不错的效果。)
然而当我们需要做一些windows程序或是服务时情况就不同了,最简单的情况,我们执行一个耗时操作,这个时候你会发现你的界面没有响应了。这是很糟糕的用户体验。用户根本不知道程序是否还在正常运行。所以我们需要有多个线程来执行不同的任务。.net为我们提用了几种实现多线程的方法,如Thread类、异步调用、ThreadPool等。但在使用中也会遇到一些问题,下面就我遇到过的问题作一个归纳和总结。
 
1.      在其他线程中更新UI
起因: .net Windows窗体使用STA模型。STA模型意味着可以在任何线程上创建窗口,但窗口一旦创建后就不能切换线程,并且对它的所有函数调用都必须在其创建线程上发生(后称GUI线程)。在其他线程中访问UI对象是危险的,虽然你也许不会马上看见异常。
解决方法:使用Control对象的Invoke,BeginInvoke和EndInvoke来封送方法到GUI线程上执行。
提示
1)      可以使用Form对象的InvokeRequired属性来判断是否需要封送;
2)      异步操作的回调也不是在UI线程上执行,所以必须封送;
3)      不必在System.Windows.Forms.Timer事件处理器中封送,该对象不同于.NetFramework中另外两个记时器(分别为System.Timers.Timer和System.Threading.Timer),System.Windows.Forms.Timer不是多线程的。同时你应该注意不要在System.Windows.Forms.Timer的事件处理器中执行长时间操作。这样同样会挂起UI。
示例
        private MethodInvoker Invoker;
private void btnStart_Click(object sender, EventArgs e)
        {
            Invoker = new MethodInvoker(AsyncCallMethod);
            //开始异步调用,此方法将马上返回,并在新线程上调用AsyncCallMethod,当调用结束时将执行AsyncCallback回调
            Invoker.BeginInvoke(new AsyncCallback(AsyncCallback), null);
            lblStatus.Text = "调用开始";
            btnStart.Enabled = false;
        }
 
        private void AsyncCallMethod()
        {
            //执行一些耗时操作
            System.Threading.Thread.Sleep(10000);
        }
 
        private void AsyncCallback(IAsyncResult ar)
        {
            Invoker.EndInvoke(ar);
            //判断是否在GUI线程上
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new MethodInvoker(UpdateUI));
            }
        }
 
        private void UpdateUI()
        {
            lblStatus.Text = "异步调用结束";
            btnStart.Enabled = true;
}
 
2.      避免多个线程同时访问一段代码
起因:在多线程程序中有时需要做到某些操作同时只能允许一个线程调用。如某property的get和set如果在多线程程序中使用,两个线程同时进入了get和set,一个线程为属性写入了新值,而另一个线程取回的则是以前的值。这往往是不希望看到的。
解决方法:在需要同步的地方使用互斥锁。只有获取到锁才执行语句,未获取到锁将等待,直到获取到锁。
提示
1)      C#中的lock语句,VB.NET中的synclock语句相当于Monitor的Enter和Exit。
2)      在set和get中分别加上lock或都加上lock可以实现“只允许一个线程写,写入时其他线程可以读”、“只允许一个线程写,写入时不可读”等多种效果。
3)      只能获取引用对象的互斥锁。
4)      可以简单的使用lock (this)来获取当前对象的互斥锁。
5)      当你使用Monitor的时候一定将Exit放入Finally块中,否则当其间代码出现异常时锁就不能被释放,而使用lock/synclock则避免了这种情况
示例
        private object objTest;
        public object ObjTest
        {
            get
            {
                lock (objTest)
                {
                    return objTest;
                }
            }
            set
            {
                lock (objTest)
                {
                    objTest = value;
                }
            }
        }
 
3.      在多线程中访问某个值类型变量
起因:当多个线程同时访问值类型字段时会出现脏读
解决方法:Interlocked对象提供了对值类型变量的原子操作。
提示:提供递增,递减,比较,更换值等操作
示例
    using System.Threading;
 
    public class ThreadSafe
    {
        private int totalValue = 0;
 
        public int Total
        {
            get { return totalValue; }
        }
 
        public int AddToTotal(int addend)
        {
            int initialValue, computedValue;
            do
            {
                // totalValue当前值缓存在本地变量
                initialValue = totalValue;
 
                // 计算缓存值与增加量的和
                computedValue = initialValue + addend;
 
                //比较缓存值与当前totalValue是否一样,是则更新totalValuecomputedValue
                //不相等则再次循环
            } while (initialValue != Interlocked.CompareExchange(
                ref totalValue, computedValue, initialValue));
 
            return computedValue;
        }
}
 
4.      实现线程间通信
起因:多线程程序中有时需要做到某个线程等待其他线程通知才继续执行。
解决方法:使用ManualResetEvent和AutoResetEvent
提示:AutoResetEvent和ManualResetEvent的区别在于ManualReset在发送信号后后将一直保持发信号状态,直到被Reset;而AutoResetEvent发送信号后在等待线程恢复执行后由系统Reset
示例
    class Program
    {
        static System.Threading.ManualResetEvent AcceptDone = new System.Threading.ManualResetEvent(true);
 
        static void Main(string[] args)
        {
            Socket objSock = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            IPEndPoint LocalEndPoint = new IPEndPoint(IPAddress.Any,8088);
            objSock.Bind(LocalEndPoint);
            objSock.Listen(100);
            while(true){//循环等待连接
                AcceptDone.Reset();
 
                Console.WriteLine("等待连接");
 
                //异步接受连接
                objSock.BeginAccept(new AsyncCallback(AcceptCallback), objSock);
 
                //使用ManualResetEvent等待连接完毕
                AcceptDone.WaitOne();
            }
        }
 
        static void AcceptCallback(IAsyncResult ar)
        {
            Socket handler = ((Socket)ar.AsyncState).EndAccept(ar);
            Console.WriteLine("收到一个连接");
            Console.WriteLine("IP:{0}", ((IPEndPoint)handler.RemoteEndPoint).Address);
            //设置ManualResetEvent状态,让主线程继续接受连接
            AcceptDone.Set();
            System.Threading.ManualResetEvent ReadDone = new System.Threading.ManualResetEvent(true);
            StateObject state = new StateObject();
            state.workSocket = handler;
            state.ReadDone = ReadDone;
            while (handler.Connected)
            {
                state.ReadDone.Reset();
                Array.Clear(state.buffer, 0, state.buffer.Length);
                handler.BeginReceive(state.buffer, 0, state.BufferSize, SocketFlags.None,
                new System.AsyncCallback(readCallback), state);
                state.ReadDone.WaitOne();
            }
            Console.WriteLine(state.sb.ToString());
        }
 
        static void readCallback(IAsyncResult ar)
        {
            StateObject State = (StateObject)ar.AsyncState;
 
            if (State.workSocket.Connected)
            {
                int Read = State.workSocket.EndReceive(ar);
                if (Read > 0)
                {
                    State.sb.Append(System.Text.Encoding.GetEncoding("gb2312").GetString(State.buffer, 0, Read));
                }
                else
                {
                    State.workSocket.Close();
                }
                State.ReadDone.Set();
            }
        }
    }
 
    class StateObject{
       public Socket workSocket = null;
       public int BufferSize = 1024;
       public Byte[] buffer = new byte[1024];
       public System.Text.StringBuilder sb = new System.Text.StringBuilder();
       public System.Threading.ManualResetEvent ReadDone;
    }
 
       其他值得注意的:
使用lock的时候一定要防止死锁。如线程a取得了对象A的锁,而线程b取得了对象B的锁,这时线程a又去获取对象B的锁,线程b去获取对象A的锁,这时候线程a/b都不可能再继续运行下去,即形成了死锁。
使用异步调用必须调用其EndInvoke方法。否则线程不能得到正常释放。线程池中的线程可能会被用完。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
C#多线程:深入了解线程同步lock,Monitor,Mutex,同步事件和等待句柄(中)...
AutoResetEvent与ManualResetEvent区别
细说C#多线程那些事:线程基础
.Net CLR 中的同步机制(三): AutoResetEvent和ManualResetEvent
AutoResetEvent和ManualResetEvent
浅谈线程池(下):相关试验及注意事项 - 老赵点滴 - 追求编程之美 - 博客园
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服