(21)多线程实例应用:双色球(6红+1蓝)
一、需求
1.双色球: 投注号码由6个红色球号码和1个蓝色球号码组成。
2.红色球号码从01--33中选择,红色球不能重复。
3.蓝色球号码从01--16中选择。
4.最终结果7个号码:6+1;即33选6(红)+ 16选1(蓝)
5.产品:
能用;用户放心使用;
原则:靠运气,不能有暗箱操作,号码开奖的随机性。
6.做法思路:
(1)从左往右---有序变化
(2)从右往左---有序变化
(3)同一时刻,球号码都变化~~
(4)可以做到让所有的球都变化,且都是相互独立的变化,随机性(推荐)
二、程序
1、界面:6个红球label显示LblRedNum1-LblRedNum6,一个蓝球LblBlue。
两个Button,BtnStart与BtnStop。一个listBox1.
2、代码:
private string[] redNums = Enumerable.Range(1, 33).Select(i => i.ToString("00")).ToArray();
private string[] blueNums = Enumerable.Range(1, 16).Select(i => i.ToString("00")).ToArray();
private object lockObj = new object();
private List<Task> tasks = new List<Task>();
private CancellationTokenSource cts;
private async void BtnStart_Click(object sender, EventArgs e)
{
BtnStart.Enabled = false;
BtnStop.Enabled = true;
cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
//初始化,置00
foreach (Label lbl in Controls.OfType<Label>().Where(b => b.Name.Contains("Red")))
{ lbl.Text = "00"; }
//开7个线程
foreach (Label lbl in Controls.OfType<Label>())
{
if (lbl.Name.Contains("Blue"))
{
tasks.Add(Task.Run(async () =>//蓝球显示
{
while (!ct.IsCancellationRequested)
{
await Task.Delay(200);
string strBlue = blueNums[GetRandom(0, 16)];
lbl.Invoke(new Action(() =>
{
lbl.Text = strBlue;
}));
}
}));
}
else
{
tasks.Add(Task.Run(async () =>//红球显示
{
while (!ct.IsCancellationRequested)
{
await Task.Delay(200);
int idx = GetRandom(0, 33);
string strRed = redNums[idx];
lock (lockObj)
{
List<string> list = GetCurNumList();
if (!list.Contains(strRed))
{
lbl.Invoke(new Action(() => { lbl.Text = strRed; }));
redNums[idx] = strRed;
}
}
}
}));
}
}
await Task.Run(() =>//任务取消时显示
{
Task.WaitAll(tasks.ToArray());
Invoke(new Action(() =>
{
List<string> list = new List<string>();
foreach (Label lbl in this.Controls.OfType<Label>().Where(b => b.Name.Contains("Red")))
{
list.Add(lbl.Text);
}
list.Sort();
list.Add(LblBlue.Text);
listBox1.Items.Add(string.Join(",", list.ToArray()));
})
);
});
}
private List<string> GetCurNumList()//返回当前红球列表
{
List<string> list = new List<string>();
foreach (Label lbl in this.Controls.OfType<Label>().Where(b => b.Name.Contains("Red")))
{ Invoke(new Action(() => { list.Add(lbl.Text); })); }
return list;
}
private int GetRandom(int min, int max)//产生强随机数
{
byte[] bytes = new byte[4];
using (var rng = RandomNumberGenerator.Create())
{ rng.GetBytes(bytes); }
int seed = BitConverter.ToInt32(bytes, 0);
return new Random(seed).Next(min, max);
}
private void BtnStop_Click(object sender, EventArgs e)
{
cts.Cancel();
BtnStart.Enabled = true;
BtnStop.Enabled = false;
}
private void Form1_Load(object sender, EventArgs e)
{ BtnStop.Enabled = false; }
三、细节
1、创建两个数组,根据随机产生的索引,从而得到随机的数。
比如,随机产生索引3,那么对于红球redNum[3]就是随机的产生的数
随机数用强随机数。根据自定义返回对应的字节。因为需要种子seed是int,所以需要4个字节即可,然后用BitConvert静态函数转换得取seed,从而产生较为真实的随机数。
2、蓝球用一个线程,红球用6个线程同时运行。
蓝球,不用管。
红球来自01-33不能重复,因此每次都得从6个红球中比较是否有相同的,相同则再次重新产生,直到得到6个不同的数为止。
6个线程一样这样操作,但容易竞争,比如都比较出与当前6个label数字不一样的,但这两个线程都产生的是相同的数字,比如是5,那么最后就会有重复的情况,为此设置互斥锁,产生数字时只准一个线程进去,改变当前label列表后,再退出,第二个线程再进去比较,得出不同后更新列表,再退出,如此循环,可使当前label的数字一直不同。
3、结果显示。
为了防止死锁,开一个子线程来等待前面7个线程的结束,并使用await防止假死。
另外,对6个红球排序后,把蓝球放在最末,加入listbox1显示。(不能全部排序,红蓝是两部分)