C#窗体多线程实现生产者消费者模型(通过回调函数修改窗体线程控件)
目录
C#窗体多线程实现生产者消费者模型(通过回调函数修改窗体线程控件)一、运行结果二、关键代码详解2.1变量2.2开始运行2.3生产者线程的代码2.4消费者线程的代码2.5回调函数部分2.6更新boxes和信息栏2.7停止运行 三、所有代码展示本文介绍C#窗体多线程的运用。工作线程由于不和窗体线程共享资源故无法直接修改窗体控件,故可使用回调函数通知窗体线程做出相应修改。
通过实现生产者消费者(使用信号量)的例子来阐述多线程回调函数的运用。
一、运行结果
图1-1图1-2 本程序窗体分为三个模块如图1-1。左上角为初始化模块,填入生产者数量,消费者数量后可以点击begin开始运行,也可以点击stop结束运行;左下角为形象显示模块,有五个boxes代表buffer的容量,未装填时为空,装填后为全黑,该模块左下角为提示信息;右侧为运行具体细节展示,如图1-2.
二、关键代码详解
2.1变量
信号量filled_num,empty_num分别代表buffer中占用资源数量和空闲资源数量,mutex控制互斥访问资源,producers,consumers数组分别存储生产者消费者进程。两个sleep变量代表各自的休眠时间。
Semaphore filled_num; Semaphore empty_num; Mutex mutex; Thread[] producers; Thread[] consumers; //一个生产者生产一个产品的用时,消费者同 const int ProduceSleep = 1000; const int ConsumeSleep = 500; //buffer容量 const int SemaphoreNum = 5; bool isStart;
2.2开始运行
若未开始,则根据输入情况为生产者消费者创建相应数量的线程(用生产者的代码produce和消费者的代码consume,都有一个输入i作为线程代号),线程都设置为后台线程并且开始执行。
//当生产消费未开始时,创建semaphore和生产消费者的进程 private void SetNum(object sender, RoutedEventArgs e) { if(!isStart) { try { filled_num = new Semaphore(0, SemaphoreNum); empty_num = new Semaphore(SemaphoreNum, SemaphoreNum); Show.AppendText(""); consumers = new Thread[int.Parse(ConsumerNum.Text.Trim())]; producers = new Thread[int.Parse(ProducerNum.Text.Trim())]; } catch(Exception) { ShowInfo.Content = "Wrong entering, please correct."; return; } mutex = new Mutex(); isStart = true; for (int i = 0; i < int.Parse(ProducerNum.Text.Trim()); i++) { producers[i] = new Thread(() => { produce(i); }); producers[i].IsBackground = true; producers[i].Start(); } //创建生产者消费者线程 for (int i = 0; i < int.Parse(ConsumerNum.Text.Trim()); i++) { consumers[i] = new Thread(() => { consume(i); }); consumers[i].IsBackground = true; consumers[i].Start(); } ShowInfo.Content = "Running."; } else { ShowInfo.Content = "You have to stop first."; } }
2.3生产者线程的代码
循环执行代码,若通过empty_num.waitone函数判断信号量empty_num是否有空闲,若有则进入生产者的休眠时间,休眠结束用mutex.waitone查看buffer是否空闲,若空闲调用update(ID)(update中进行了回调以通知主线程更新界面),更新后释放mutex并release一个filled_num信号量。
//生产者的produce,一秒钟生产一个,涂黑一个 public void produce(int ID) { while (true) { try { empty_num.WaitOne(); Thread.Sleep(ProduceSleep); mutex.WaitOne(); update(ID); mutex.ReleaseMutex(); filled_num.Release(); } catch (Exception) { return; } } }
2.4消费者线程的代码
与生产者线程的相似,只不过是先信号量filled_num.waitone,最后释放的是empty_num。输入update的参数为负数代表是消费者进程。
//消费者的consume, 500毫秒消费一个,涂灰一个 public void consume(int ID) { while(true) { try { filled_num.WaitOne(); Thread.Sleep(ConsumeSleep); mutex.WaitOne(); update(-1 * ID); mutex.ReleaseMutex(); empty_num.Release(); } catch(Exception) { return; } } }
2.5回调函数部分
ID为负数则为消费者,正数为生产者。先通过窗体的storepanel(也就是显示buffer的五个box所在的panel)和Show(也就是右侧的信息显示文本框)的Dispatcher查看是否可回调(其底层实现原理也是消息队列),若都可以,则通过Update委托调用UpdateBoxes(更新boxes)和UpdateShow(更新右侧信息栏)。
//生产者消费者的回调函数 public void update(int ID) { //无法访问则使用回调函数 if (!Storepanel.Dispatcher.CheckAccess()&&!Show.Dispatcher.CheckAccess()) { //声明,并实例化回调 Update d = UpdateBoxes; //使用回调 Storepanel.Dispatcher.Invoke(d, ID); Update f = UpdateShow; Show.Dispatcher.Invoke(f, ID); } }
2.6更新boxes和信息栏
UpdateBox若是消费者调用则id为负,若是生产者则为正。若是生产者则正向遍历StorePanel中的boxes(实际上是label,通过改变背景色标示是否为空)寻找一个空闲label改变其backgroud值,消费者亦然。
UpdateShow 更新窗体中的信息,采取方式与UpdateBox相似。
这两个函数都通过委托使得生产者消费者进程能够回调更新窗体。
private delegate void Update(int index); //更新窗体中的storestack,index绝对值为生产者或消费者的编号,负的是消费者 public void UpdateBoxes(int ID) { if(ID<0) {//消费者 for (int i = 0; i < Storepanel.Children.Count; i++) { Label temp = Storepanel.Children[i] as Label; if (temp.Background != Brushes.Gray) { temp.Background = Brushes.Gray; break; } } } else {//生产者 for (int i = 0; i < Storepanel.Children.Count; i++) { Label temp = Storepanel.Children[i] as Label; if (temp.Background != Brushes.Black) { temp.Background = Brushes.Black; break; } } } } //更新窗体中Show的信息 public void UpdateShow(int ID) { if (ID < 0) { Show.AppendText("Consumer"+ID*-1+" consumes an item.\n"); } else { Show.AppendText("Producer" + ID + " produces an item.\n"); } Show.ScrollToEnd(); }
2.7停止运行
通过原先记录的线程数组,遍历线程用Abort函数释放。
private void StopButton_Click(object sender, RoutedEventArgs e) { if (isStart) { //abort后台线程 for (int i = 0; i < producers.Length; i++) { producers[i].Abort(); } for (int i = 0; i < consumers.Length; i++) { consumers[i].Abort(); } //复原boxes for (int i = 0; i < Storepanel.Children.Count; i++) { Label temp = Storepanel.Children[i] as Label; temp.Background = Brushes.Gray; temp.Content = "空"; } ShowInfo.Content = "Stopped."; isStart = false; Show.AppendText("----------------------------------------\n"); } else { ShowInfo.Content = "You have to start first."; } }
三、所有代码展示
代码中由于为了调试方便和运行流畅使用了较多的try-catch,但catch之后未作处理,读者可自行完善。
using System;using System.Windows;using System.Windows.Controls;using System.Windows.Media;using System.Threading;namespace ProduserAndConsumer{ /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { Semaphore filled_num; Semaphore empty_num; Mutex mutex; Thread[] producers; Thread[] consumers; //一个生产者生产一个产品的用时,消费者同 const int ProduceSleep = 1000; const int ConsumeSleep = 500; //buffer容量 const int SemaphoreNum = 5; bool isStart; public MainWindow() { InitializeComponent(); this.Closing += Window_Closing; isStart = false; for (int i = 0; i < Storepanel.Children.Count; i++) { Label temp = Storepanel.Children[i] as Label; temp.Background = Brushes.Gray; temp.Content = "空"; } } //当生产消费未开始时,创建semaphore和生产消费者的进程 private void SetNum(object sender, RoutedEventArgs e) { if(!isStart) { try { filled_num = new Semaphore(0, SemaphoreNum); empty_num = new Semaphore(SemaphoreNum, SemaphoreNum); Show.AppendText(""); consumers = new Thread[int.Parse(ConsumerNum.Text.Trim())]; producers = new Thread[int.Parse(ProducerNum.Text.Trim())]; } catch(Exception) { ShowInfo.Content = "Wrong entering, please correct."; return; } mutex = new Mutex(); isStart = true; //创建生产者消费者线程 for (int i = 0; i < int.Parse(ProducerNum.Text.Trim()); i++) { producers[i] = new Thread(() => { produce(i); }); producers[i].IsBackground = true; producers[i].Start(); } for (int i = 0; i < int.Parse(ConsumerNum.Text.Trim()); i++) { consumers[i] = new Thread(() => { consume(i); }); consumers[i].IsBackground = true; consumers[i].Start(); } ShowInfo.Content = "Running."; } else { ShowInfo.Content = "You have to stop first."; } } //生产者的produce,一秒钟生产一个,涂黑一个 public void produce(int ID) { while (true) { try { empty_num.WaitOne(); Thread.Sleep(ProduceSleep); mutex.WaitOne(); update(ID); mutex.ReleaseMutex(); filled_num.Release(); } catch (Exception) { return; } } } //消费者的consume, 500毫秒消费一个,涂灰一个 public void consume(int ID) { while(true) { try { filled_num.WaitOne(); Thread.Sleep(ConsumeSleep); mutex.WaitOne(); update(-1 * ID); mutex.ReleaseMutex(); empty_num.Release(); } catch(Exception) { return; } } } //生产者消费者的回调函数 public void update(int ID) { //无法访问则使用回调函数 if (!Storepanel.Dispatcher.CheckAccess()&&!Show.Dispatcher.CheckAccess()) { //声明,并实例化回调 Update d = UpdateBoxes; //使用回调 Storepanel.Dispatcher.Invoke(d, ID); Update f = UpdateShow; Show.Dispatcher.Invoke(f, ID); } } private delegate void Update(int index); //更新窗体中的storestack,index绝对值为生产者或消费者的编号,负的是消费者 public void UpdateBoxes(int ID) { if(ID<0) {//消费者 for (int i = 0; i < Storepanel.Children.Count; i++) { Label temp = Storepanel.Children[i] as Label; if (temp.Background != Brushes.Gray) { temp.Background = Brushes.Gray; break; } } } else {//生产者 for (int i = 0; i < Storepanel.Children.Count; i++) { Label temp = Storepanel.Children[i] as Label; if (temp.Background != Brushes.Black) { temp.Background = Brushes.Black; break; } } } } //更新窗体中Show的信息 public void UpdateShow(int ID) { if (ID < 0) { Show.AppendText("Consumer"+ID*-1+" consumes an item.\n"); } else { Show.AppendText("Producer" + ID + " produces an item.\n"); } Show.ScrollToEnd(); } //窗口关闭则关闭所有线程 public void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (isStart) { for (int i = 0; i < producers.Length; i++) { producers[i].Abort(); } for (int i = 0; i < consumers.Length; i++) { consumers[i].Abort(); } } } private void StopButton_Click(object sender, RoutedEventArgs e) { if (isStart) { //abort后台线程 for (int i = 0; i < producers.Length; i++) { producers[i].Abort(); } for (int i = 0; i < consumers.Length; i++) { consumers[i].Abort(); } //复原boxes for (int i = 0; i < Storepanel.Children.Count; i++) { Label temp = Storepanel.Children[i] as Label; temp.Background = Brushes.Gray; temp.Content = "空"; } ShowInfo.Content = "Stopped."; isStart = false; Show.AppendText("----------------------------------------\n"); } else { ShowInfo.Content = "You have to start first."; } } }}
由于本实验完成的时间较久远所以一些记录的参考文献找不着了,如有雷同,不胜荣幸(可以提醒我加上参考文献)。
文件下载链接:
http://www.lolxun.com/resource/2249796.html