C#利用Task模拟多线程抢占资源

  在这篇博客中,我们将实现一个简单的模拟程序,模拟10只猴子在抢夺100,000个香蕉的过程中进行并发操作。通过这个例子,我们将学习如何在C#中使用异步编程 (async/await)、多线程 (Task.Run) 以及线程安全机制(如 lock)来处理共享资源的并发访问

项目需求

  1. 游戏背景:有100,000个香蕉,10只猴子参与抢夺。
  2. 每只猴子的行为:每只猴子在抢夺香蕉时,每次随机抓取1到100个香蕉。每次抓取后,剩余香蕉数会减少,直到没有香蕉可以抢夺为止。
  3. 线程安全:由于多个猴子会并发访问共享资源(剩余香蕉数量),因此需要确保线程安全,避免在多线程环境下发生数据竞态。

设计思路

  1. 共享资源:香蕉的数量是共享资源。我们需要使用 lock 来确保每次只有一个猴子可以修改剩余香蕉数量,从而避免竞争条件。
  2. 异步操作:每只猴子的抢夺行为是异步执行的,因此我们使用 Task.Run 来并行处理每个猴子的任务,并使用 async/await 来等待所有任务完成。
  3. UI更新:在任务完成后,我们需要在UI线程中更新界面,显示每只猴子抢到的香蕉数量。

代码实现

以下是完整的代码实现,模拟了多个猴子并发抢香蕉的过程。

namespace Monkey
{
    public partial class Form1 : Form
    {
        // 总香蕉数量
        private const int TotalBananas = 100000;
        // 剩余香蕉数量
        private int bananasRemaining;
        // 锁对象
        private readonly object lockObject = new object();
        // 猴子列表
        private List<Monkey> monkeys = new List<Monkey>();
        // 随机数生成器
        private Random random = new Random();

        public Form1()
        {
            InitializeComponent();
            InitializeMonkeys(); // 初始化猴子
            bananasRemaining = TotalBananas; // 设置剩余香蕉数量为总数量
        }

        /// <summary>
        /// 初始化猴子的方法
        /// </summary>
        private void InitializeMonkeys()
        {
            monkeys.Clear(); // 清空之前的猴子数据
            // 创建10只猴子并添加到列表中
            for (int i = 0; i < 10; i++)
            {
                Monkey monkey = new Monkey($"猴子 {i + 1}");
                monkeys.Add(monkey);
            }
        }

        /// <summary>
        /// 点击  开始   按钮的事件处理方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void btnStart_Click(object sender, EventArgs e)
        {
            // 为新一轮重置剩余香蕉数量
            bananasRemaining = TotalBananas;

            // 重置每只猴子抓到的香蕉数量
            foreach (var monkey in monkeys)
            {
                monkey.Reset();
            }

            try
            {
                var tasks = new List<Task>();

                // 为每只猴子创建抢夺香蕉的任务
                foreach (var monkey in monkeys)
                {
                    tasks.Add(Task.Run(() => monkey.GrabBananas(lockObject, ref bananasRemaining, random)));
                }

                // 等待所有猴子的任务完成
                await Task.WhenAll(tasks);
                // 显示抢夺结果
                DisplayResults();
            }
            catch (Exception ex)
            {
                // 捕获异常并显示错误信息
                MessageBox.Show($"发生错误: {ex.Message}");
            }
        }

        /// <summary>
        /// 显示抢夺结果的方法
        /// </summary>
        private void DisplayResults()
        {
            // 使用Invoke确保在UI线程中更新
            this.Invoke((MethodInvoker)delegate
            {
                // 构建结果字符串
                string result = $"剩余香蕉数量: {bananasRemaining}\n\n";
                foreach (var monkey in monkeys)
                {
                    result += $"{monkey.Name} 抢到了   {monkey.BananasGrabbed}   个香蕉。\n";
                }
                // 显示结果消息框
                MessageBox.Show(result);
            });
        }
    }

    
    public class Monkey
    {
        public string Name { get; } // 猴子的名称
        public int BananasGrabbed { get; private set; } // 抢到的香蕉数量

        public Monkey(string name)
        {
            Name = name;
            BananasGrabbed = 0; // 初始化抢到的数量
        }

        /// <summary>
        /// 重置抓到的香蕉数量
        /// </summary>
        public void Reset()
        {
            BananasGrabbed = 0; // 为新一轮重置数量
        }

        /// <summary>
        /// 抢夺香蕉的方法
        /// </summary>
        /// <param name="lockObject"></param>
        /// <param name="bananasRemaining"></param>
        /// <param name="random"></param>
        public void GrabBananas(object lockObject, ref int bananasRemaining, Random random)
        {
            while (true)
            {
                lock (lockObject) // 线程安全锁
                {
                    if (bananasRemaining <= 0) { 
                        break; // 如果没有香蕉了,退出循环
                    }
                    // 随机抢夺1到100个香蕉
                    int grabAmount = random.Next(1, 101);
                    if (grabAmount > bananasRemaining)
                    {
                        grabAmount = bananasRemaining; // 如果抢的数量超过剩余数量,则只抢剩余数量
                    }

                    bananasRemaining -= grabAmount; // 更新剩余香蕉数量
                    BananasGrabbed += grabAmount; // 更新猴子抓到的数量
                }

                // 使用延迟,确保每只猴子都能抢到
              Task.Delay(random.Next(0,2 )).Wait(); // 小延迟,模拟抢夺过程
            }
        }
    }
}

代码分析

  1. 初始化和设置

    • Form1 的构造函数中,我们初始化了10只猴子,并为它们分配了名称和初始的抓到香蕉数量。总香蕉数设置为100,000个。
  2. 抢香蕉逻辑

    • 每只猴子通过 GrabBananas 方法抢夺香蕉。方法通过 lock 关键字保证了对剩余香蕉数量的线程安全访问。
    • 每次抢夺时,猴子随机获取1到100个香蕉,如果请求的数量超过了剩余的香蕉,就只抢剩余的部分。
    • 为了模拟猴子抢夺过程的延迟,使用了 Task.Delay
  3. 任务执行和UI更新

    • 当点击 "开始" 按钮时,程序启动所有猴子的抢香蕉任务,并使用 Task.WhenAll 等待所有任务完成。
    • 当所有猴子完成任务后,我们通过 DisplayResults 方法显示每只猴子抓到的香蕉数量。
  4. 线程安全

    • 由于多个猴子同时修改 bananasRemaining 变量,我们使用了 lock 来保证对这个共享资源的访问是安全的。

designer代码如下:

namespace Monkey
{
    partial class Form1
    {
        // 按钮组件
        private System.Windows.Forms.Button btnStart;

        /// <summary>
        /// 初始化UI组件的方法
        /// </summary>
        private void InitializeComponent()
        {
            // 创建按钮实例
            this.btnStart = new System.Windows.Forms.Button();
            this.SuspendLayout(); // 开始布局

            // 
            // btnStart
            // 
            this.btnStart.Location = new System.Drawing.Point(100, 120); // 设置按钮位置,稍微向下移动
            this.btnStart.Name = "btnStart"; // 设置按钮名称
            this.btnStart.Size = new System.Drawing.Size(120, 60); // 设置按钮大小,稍微增大
            this.btnStart.TabIndex = 0; // 设置按钮的Tab索引
            this.btnStart.Text = "开始"; // 设置按钮显示的文本为中文
            this.btnStart.UseVisualStyleBackColor = true; // 使用系统视觉样式
            this.btnStart.Click += new System.EventHandler(this.btnStart_Click); // 绑定点击事件

            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); // 设置自动缩放的尺寸
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; // 使用字体自动缩放模式
            this.ClientSize = new System.Drawing.Size(320, 240); // 设置窗体大小
            this.Controls.Add(this.btnStart); // 将按钮添加到窗体控件集合中
            this.Name = "Form1"; // 设置窗体名称
            this.Text = "猴子香蕉模拟器"; // 设置窗体标题为中文
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; // 窗体启动时居中显示
            this.BackColor = System.Drawing.Color.LightYellow; // 设置窗体背景颜色为淡黄色
            this.ResumeLayout(false); // 结束布局
        }
    }
}

运行截图:

总结

通过这个例子,我们可以学到如何在 C# 中实现并发编程,特别是在多线程环境下如何管理共享资源(如剩余香蕉数量)。lock 确保了线程安全,而 Task.Runasync/await 让我们的代码可以并行执行而不阻塞UI线程。通过合理的设计和线程管理,我们实现了一个模拟猴子抢香蕉的游戏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值