C#实现多线程与文件

C#实现多线程与文件 详细教程

一、引言

这次实验,通过实现经典的生产者与消费者问题, 加深对多线程的理解与文件的操作.

任务

2.2 构建线程A1、A2… Ak(k>=3)和线程B的程序(k生产者和单消费者)。A1、A2… Ak从磁盘各自读取一个文本文件,写入到内存中的固定的容器(如Pool)。A1、A2… Ak读取每一行时,都会休眠,然后在随机的时间(10–100 ms)醒来继续尝试执行。程序要求按照A1、A2… Ak的顺序写入Pool。B会观察Pool的状态,如果有新数据,则进行读取,否则B处于等待状态。注意,A1、A2… Ak不能互相干扰。当所有的文件被读取完毕,且B读取完毕时,程序结束。

二、实验环境

Visual stdio 2017
Windows窗体应用

三、实验过程

步骤:

  1. 创建窗体应用程序和文件:

  2. 构建生产者

  3. 构建消费者

  4. 构建生产消费过程

1. 创建窗体应用程序和文件:

创建窗体应用程序

建立两个richTextBox放置生产和消费,分别用label控件标注生产者和消费者

一个button控件 表示开始
在这里插入图片描述
对于richTextBox放置生产和消费的text进行初始化

string producer_text = "";//生产者的richtextbox的内容
string consumer_text = "";//消费者的richtextbox的内容
创建文本文件

在bin/Debug下创建十个文本文件 用于生产者生产

2. 构建生产者

加入命名空间

using System.Threading;
using System.Collections;
using System.IO;

参考老师上课讲过的案例与书本自带的程序代码与PPT之后,我发现能用mark来实现线程互斥的一个部分,

若mark为true, 不能放数据,put/producer线程等待,

若mark为false,不能取数据,get/consumer线程等待,

之后结合后续代码进行说明

bool mark = false;//若mark为true, 不能放数据,put/producer线程等待
                  //若mark为false,不能取数据,get/consumer线程等待
文件的操作

此块代码中用到了文件的操作:

​ C#将文件看成是顺序的字节流,也称为文件流。
  文件流是字节序列的抽象概念,文件可以看成是存储在磁盘上的一系列二进制字节信息。
  C#用文件流对其进行输入、输出操作,例如,读取文件信息、向文件写入信息。
  C#提供Stream类(System.IO成员)是所有流的基类,由它派生出文件流FileStream和缓冲区流BufferedStream。

以下解释此代码中用到的操作:

加入命名空间:System.IO

OpenRead:打开现有文件以进行读取

FileStream:公开以文件为主的Stream,既支持同步读写操作,也支持异步读写操作

StreamReader:实现一个TextReader,使其以一种特定的编码从字节流中读取字符

            //A1、A2... Ak从磁盘各自读取一个文本文件 
            //打开读
            FileStream fs = File.OpenRead("A" + Convert.ToString(sq) + ".txt"); 
            StreamReader sr = new StreamReader(fs,Encoding.Default);

            //
            while(sr.Peek()>-1)
            {
                //将文件中文本读入pustr中,A1、A2... Ak读取每一行
                pustr += sr.ReadLine() + "\n";
                //读取每一行时,都会休眠,然后在随机的时间(10--100 ms)醒来继续尝试执行。
                Thread.Sleep(r.Next(10, 100));
            }
线程互斥

​ 多个线程在同时修改共享数据时可能发生错误,这样的共享数据称为临界区。
  线程互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

​ 也可以是很多人读取 只能一个人写.

用Monitor类实现线程互斥

Monitor类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问代码块(通常称为临界区)的能力。
  当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。还可以使用Monitor来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。

Monitor类的主要静态方法如下:

Enter():获取对象锁。此操作同样会标记临界区的开头。其他任何线程都不能进入临界区,除非它使用其他锁定对象执行临界区中的指令。
Wait():释放对象上的锁以便允许其他线程锁定和访问该对象。在其他线程访问对象时,调用线程将等待。脉冲信号用于通知等待线程有关对象状态的更改。
Pulse():向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更改,并且锁的所有者准备释放该锁。等待线程被放置在对象的就绪队列中以便它可以最后接收对象锁。一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。
Exit():释放对象上的锁。此操作还标记受锁定对象保护的临界区的结尾。

生产者总代码:

		ArrayList pool = new ArrayList();//添加sing System.Collections;
        int seq = 1;//程序要求按照A1、A2… Ak的顺序写入Pool,这里用于确定A1、A2… Ak顺序;

        //生产者/put
        private void put(object sq)
        {
            string pustr = "";
            Random r = new Random();
            Monitor.Enter(this);加排它锁
            while(mark||seq!=(int)sq)
            {若mark为true, 不能放数据,put/producer线程等待
                Monitor.Pulse(this);
                Monitor.Wait(this);
            }
            mark = !mark;//将mark由false改为true

            //A1、A2... Ak从磁盘各自读取一个文本文件 
            //打开读
            FileStream fs = File.OpenRead("A" + Convert.ToString(sq) + ".txt");
            StreamReader sr = new StreamReader(fs,Encoding.Default);

            //
            while(sr.Peek()>-1)
            {
                //将文件中文本读入pustr中,A1、A2... Ak读取每一行
                pustr += sr.ReadLine() + "\n";
                //读取每一行时,都会休眠,然后在随机的时间(10--100 ms)醒来继续尝试执行。
                Thread.Sleep(r.Next(10, 100));
            }

            //写入到内存中的固定的容器(Pool)
            pool.Add(pustr);//把文件添加到容器
            producer_text += pustr;

            sr.Close();
            fs.Close();
            seq++;

            Monitor.Pulse(this);            //激活生产者线程
            Monitor.Exit(this);			//释放排它锁
        }

3. 构建消费者

 //消费者/get
 private void get()
 {
     for(int s=0;s<10;s++)
     {
         Monitor.Enter(this);
         while(!mark)//若mark为false,不能取数据,本线程等待
         {
             Monitor.Pulse(this);
             Monitor.Wait(this);
         }
         mark =! mark; //将mark由true改为false
         consumer_text += pool[pool.Count - 1];

         Monitor.Pulse(this);//激活生产者线程
         Monitor.Exit(this);//释放排它锁
     }
 }

4. 构建生产消费过程

关于线程:

线程命名空间是System.Threading,它提供了多线程程序设计的类和接口等,用以执行诸如创建和启动线程、同步多个线程、挂起线程和中止线程等任务.

下面代码有关线程方面的类和方法如下:

Thread类:用于创建并控制线程、设置其优先级并获取其状态。
Abort:在调用此方法的线程上引发ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程

Sleep:将当前线程阻塞指定的毫秒数

Start:使得线程得以按计划执行

在窗体界面中, 双击button,进行开始生产的编程:

 private void button1_Click(object sender, EventArgs e)
 {
     Thread workth_put_1 = new Thread(put);创建一个工作线程
     Thread workth_put_2 = new Thread(put);
     Thread workth_put_3 = new Thread(put);
     Thread workth_put_4 = new Thread(put);
     Thread workth_put_5 = new Thread(put);
     Thread workth_put_6 = new Thread(put);
     Thread workth_put_7 = new Thread(put);
     Thread workth_put_8 = new Thread(put);
     Thread workth_put_9 = new Thread(put);
     Thread workth_put_10 = new Thread(put);
     Thread workth_get = new Thread(get);

     workth_put_1.Start(1);//Start:使得线程得以按计划执行
     workth_put_2.Start(2);
     workth_put_3.Start(3);
     workth_put_4.Start(4);
     workth_put_5.Start(5);
     workth_put_6.Start(6);
     workth_put_7.Start(7);
     workth_put_8.Start(8);
     workth_put_9.Start(9);
     workth_put_10.Start(10);
     workth_get.Start();

     Thread.Sleep(1600);//Sleep:将当前线程阻塞指定的毫秒数

     workth_put_1.Abort();//
     workth_put_2.Abort();
     workth_put_3.Abort();
     workth_put_4.Abort();
     workth_put_5.Abort();
     workth_put_6.Abort();
     workth_put_7.Abort();
     workth_put_8.Abort();
     workth_put_9.Abort();
     workth_put_10.Abort();
     workth_get.Abort();//Abort:在调用此方法的线程上引发ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程

     richTextBox1.Text = producer_text;
     richTextBox2.Text = consumer_text;
 }

结果展示:
在这里插入图片描述
完整代码:

public partial class Form1 : Form
    {
        string producer_text = "";//生产者的richtextbox的内容
        string consumer_text = "";//消费者的richtextbox的内容‘

        bool mark = false;//若mark为true, 不能放数据,put/producer线程等待
                          //若mark为false,不能取数据,get/consumer线程等待
        
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        ArrayList pool = new ArrayList();//添加sing System.Collections;
        int seq = 1;//程序要求按照A1、A2… Ak的顺序写入Pool,这里用于确定A1、A2… Ak顺序;

        //生产者/put
        private void put(object sq)
        {
            string pustr = "";
            Random r = new Random();
            Monitor.Enter(this);加排它锁
            while(mark||seq!=(int)sq)
            {若mark为true, 不能放数据,put/producer线程等待
                Monitor.Pulse(this);
                Monitor.Wait(this);
            }
            mark = !mark;//将mark由false改为true

            //A1、A2... Ak从磁盘各自读取一个文本文件 
            //打开读
            FileStream fs = File.OpenRead("A" + Convert.ToString(sq) + ".txt"); 
            StreamReader sr = new StreamReader(fs,Encoding.Default);

            //
            while(sr.Peek()>-1)
            {
                //将文件中文本读入pustr中,A1、A2... Ak读取每一行
                pustr += sr.ReadLine() + "\n";
                //读取每一行时,都会休眠,然后在随机的时间(10--100 ms)醒来继续尝试执行。
                Thread.Sleep(r.Next(10, 100));
            }

            //写入到内存中的固定的容器(Pool)
            pool.Add(pustr);//把文件添加到容器
            producer_text += pustr;

            sr.Close();
            fs.Close();
            seq++;

            Monitor.Pulse(this);            //激活生产者线程
            Monitor.Exit(this);			//释放排它锁
        }

        //消费者/get
        private void get()
        {
            for(int s=0;s<10;s++)
            {
                Monitor.Enter(this);
                while(!mark)//若mark为false,不能取数据,本线程等待
                {
                    Monitor.Pulse(this);
                    Monitor.Wait(this);
                }
                mark =! mark; //将mark由true改为false
                consumer_text += pool[pool.Count - 1];

                Monitor.Pulse(this);//激活生产者线程
                Monitor.Exit(this);//释放排它锁
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread workth_put_1 = new Thread(put);创建一个工作线程
            Thread workth_put_2 = new Thread(put);
            Thread workth_put_3 = new Thread(put);
            Thread workth_put_4 = new Thread(put);
            Thread workth_put_5 = new Thread(put);
            Thread workth_put_6 = new Thread(put);
            Thread workth_put_7 = new Thread(put);
            Thread workth_put_8 = new Thread(put);
            Thread workth_put_9 = new Thread(put);
            Thread workth_put_10 = new Thread(put);
            Thread workth_get = new Thread(get);

            workth_put_1.Start(1);//Start:使得线程得以按计划执行
            workth_put_2.Start(2);
            workth_put_3.Start(3);
            workth_put_4.Start(4);
            workth_put_5.Start(5);
            workth_put_6.Start(6);
            workth_put_7.Start(7);
            workth_put_8.Start(8);
            workth_put_9.Start(9);
            workth_put_10.Start(10);
            workth_get.Start();

            Thread.Sleep(1600);//Sleep:将当前线程阻塞指定的毫秒数

            workth_put_1.Abort();//
            workth_put_2.Abort();
            workth_put_3.Abort();
            workth_put_4.Abort();
            workth_put_5.Abort();
            workth_put_6.Abort();
            workth_put_7.Abort();
            workth_put_8.Abort();
            workth_put_9.Abort();
            workth_put_10.Abort();
            workth_get.Abort();//Abort:在调用此方法的线程上引发ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程

            richTextBox1.Text = producer_text;
            richTextBox2.Text = consumer_text;
        }
}
  • 2
    点赞
  • 3
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:1024 设计师:我叫白小胖 返回首页
评论

打赏作者

影子__

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值