当前位置:首页 » 《随便一记》 » 正文

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

6 人参与  2022年11月09日 10:49  分类 : 《随便一记》  评论

点击全文阅读


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

目录

C#窗体多线程实现生产者消费者模型(通过回调函数修改窗体线程控件)一、运行结果二、关键代码详解2.1变量2.2开始运行2.3生产者线程的代码2.4消费者线程的代码2.5回调函数部分2.6更新boxes和信息栏2.7停止运行 三、所有代码展示
      本文介绍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


点击全文阅读


本文链接:http://zhangshiyu.com/post/47614.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1