C#窗体多线程实现生产者消费者模型(通过回调函数修改窗体线程控件)
目录
本文介绍C#窗体多线程的运用。工作线程由于不和窗体线程共享资源故无法直接修改窗体控件,故可使用回调函数通知窗体线程做出相应修改。
通过实现生产者消费者(使用信号量)的例子来阐述多线程回调函数的运用。
一、运行结果
二、关键代码详解
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