C#窗体多线程实现生产者消费者模型(通过回调函数修改窗体线程控件)

C#窗体多线程实现生产者消费者模型(通过回调函数修改窗体线程控件)


      本文介绍C#窗体多线程的运用。工作线程由于不和窗体线程共享资源故无法直接修改窗体控件,故可使用回调函数通知窗体线程做出相应修改。
      通过实现生产者消费者(使用信号量)的例子来阐述多线程回调函数的运用。

一、运行结果

图1

图1-1

图2

图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

  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值