编程工作中:
有个痛点叫做:别人的代码&自己N天前写的代码。
痛点的症状是:一时间找不到南北,相应花费时间才能缕清具体的来龙去脉。
当前比较多的解决办法是,注释规范化。这个有督促还好,若是没有,很多时候就没有注释了。这就造成了代码不容易看懂。
是否还有别的?这里说一个使用多线程的解决办法。
多线程的特点是根据线程间通讯,来实现多个线程协同工作。
一个生活中通俗的例子就是去饭馆吃饭:点菜,做菜和上菜。分成三个线程,各自工作,是不受影响的。
以下是这三个线程的状态:
点菜:等待客人,记录客人点的菜
做菜:查看点菜记录,做菜,做菜完成
上菜:等待做菜完成,上菜
点菜,把客户要点的菜记录好;做菜,读取客户的点菜记录来做菜;上菜,在有做好的菜就上菜。
在实际的多线程中,点菜,做菜和上菜,这样的关系是比较少。如果都是这样的话,那就简单多了。
实际的多线程会是,个别线程会需要多个线程满足条件后才能继续后续的工作,在后续的工作还会需要其他线程满足条件才能工作。这样,若是没有做好注解,就不好去理解具体的工作流程。
这里的解决办法是:增加线程的状态。
设置状态代码为:
uDdkr_等待客人();//“u”为“update”首字母;“Ddkr”为“等待客人”的首字母,若那天VS能识别中文首字母就不需要加“Ddkr”来做引导了。
判断线程到了哪一个状态的代码为:
if (点菜.sDdkr_等待客人)
{
}
点菜的部分类代码为:
namespace WCFState
{
public partial class 点菜
{
#region 点菜状态
public enum S
{
正在准备中,
等待客人,
记录客人点的菜,
}
private static S s状态;
public static S 点菜状态
{
get { return s状态; }
}
#region 判断当前的状态
public static bool sZzzbz_正在准备中
{
get
{
if (s状态 == S.正在准备中)
return true;
return false;
}
}
public static bool sDdkr_等待客人
{
get
{
if (s状态 == S.等待客人)
return true;
return false;
}
}
public static bool sJlkrdd_记录客人点的菜
{
get
{
if (s状态 == S.记录客人点的菜)
return true;
return false;
}
}
#endregion
#region 更新状态
private static void uZzzbz_正在准备中()
{
s状态 = S.正在准备中;
}
private static void uDdkr_等待客人()
{
s状态 = S.等待客人;
}
private static void uJlkrdd_记录客人点的菜()
{
s状态 = S.记录客人点的菜;
}
#endregion
#endregion
}
}
以上的代码若是要手写,确实头大,故做了个自动生成控件,正常情况下,创建一个自定义控件:WCFState,把以下代码复制粘贴到“WCFState.cs”里面,再改下命名空间,该自定义控件就能拿来用。具体生成的代码为:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace WCFStateDemo
{
public partial class WCFState : UserControl
{
private List<string> listData = new List<string>();
private DataGridView dgvAutoStatus;
private RichTextBox txtStatus;
private bool isGenCode = false;//是否是生成代码状态,不是生成代码状态则为列表实时显示状态
public WCFState()
{
InitializeComponent();
}
private void WCFState_Load(object sender, EventArgs e)
{
Init();
}
#region 界面初始化
private void Init()
{
if (isGenCode)
{
#region 添加输入框
txtStatus = new RichTextBox();
txtStatus.Dock = DockStyle.Fill;
txtStatus.Location = new Point(0, 0);
txtStatus.Multiline = true;
txtStatus.Name = "txtStatus";
txtStatus.Size = new Size(461, 487);
txtStatus.Font = new Font("宋体", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(134)));
#region 创建右键菜单
ToolStripMenuItem cmsOpenStatusTxt = new ToolStripMenuItem();
cmsOpenStatusTxt.Name = "cmsOpenStatusTxt";
cmsOpenStatusTxt.Size = new Size(180, 30);
cmsOpenStatusTxt.Text = "打开TXT状态文件";
cmsOpenStatusTxt.Click += new EventHandler(cmsOpenStatusTxt_Click);
ToolStripMenuItem cmsGenStatusCode = new ToolStripMenuItem();
cmsGenStatusCode.Name = "cmsGenStatusCode";
cmsGenStatusCode.Size = new Size(180, 30);
cmsGenStatusCode.Text = "生成状态相关代码";
cmsGenStatusCode.Click += new EventHandler(cmsGenStatusCode_Click);
ContextMenuStrip cmsAddStatus = new ContextMenuStrip(components);
cmsAddStatus.Name = "cmsAddStatus";
cmsAddStatus.Size = new Size(180, 50);
cmsAddStatus.Items.AddRange(new ToolStripItem[] { cmsOpenStatusTxt, cmsGenStatusCode });
#endregion
txtStatus.ContextMenuStrip = cmsAddStatus;
txtStatus.TabIndex = 1;
Controls.Add(txtStatus);
txtStatus.Text += "//生成时会同时把当前生成内容以TXT文件保存在相同的目录下,右键“打开TXT状态文件”时选择这个文件可以直接加载进来\r\n";
txtStatus.Text += "//增加规则是:一个类名和其状态名称是一行,使用逗号隔开\r\n";
txtStatus.Text += "//范例:第一行为命名空间,之后每行为:类名+“中文逗号”+状态+“中文逗号”+状态+“中文逗号”+状态 等等\r\n";
txtStatus.Text += "//使用一个生活小示例,如下:(“//”为注释符,在生成时会忽略)\r\n";
txtStatus.Text += "//namespace WCFStateDemo\r\n";
txtStatus.Text += "//点菜,正在准备中,等待客人,记录客人点的菜\r\n";
txtStatus.Text += "//做菜,正在准备中,查看点菜记录,做菜,做菜完成\r\n";
txtStatus.Text += "//上菜,正在准备中,等待做菜完成,上菜\r\n";
txtStatus.Text += "//添加好后,右键生成,会在选择的目录下生成相应的状态类,及把本次生成的内容写到TXT文档\r\n";
txtStatus.Text += ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,";
#endregion
}
else
{
#region 添加显示的列表
dgvAutoStatus = new DataGridView();
dgvAutoStatus.AllowUserToAddRows = false;
dgvAutoStatus.AllowUserToDeleteRows = false;
dgvAutoStatus.BackgroundColor = SystemColors.ButtonHighlight;
dgvAutoStatus.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgvAutoStatus.Dock = DockStyle.Fill;
dgvAutoStatus.Location = new Point(0, 0);
dgvAutoStatus.ReadOnly = true;
dgvAutoStatus.ClearSelection();//取消默认选中效果
dgvAutoStatus.Name = "dgvAutoStatus";
dgvAutoStatus.RowHeadersVisible = false;
dgvAutoStatus.RowTemplate.Height = 23;
dgvAutoStatus.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvAutoStatus.Size = new Size(461, 487);
dgvAutoStatus.TabIndex = 1;
Controls.Add(dgvAutoStatus);
#endregion
#region 创建定时器
Timer timeRefresh = new Timer(components);
timeRefresh.Enabled = true;
timeRefresh.Interval = 600;
timeRefresh.Tick += new EventHandler(this.timeRefresh_Tick);
#endregion
}
}
#endregion
#region 生成状态代码
private void cmsOpenStatusTxt_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "txt文本格式(*.txt)|*.txt";
dialog.ValidateNames = true;
dialog.CheckPathExists = true;
dialog.CheckFileExists = true;
if (dialog.ShowDialog() == DialogResult.OK)
{
OpenStatusTxt(dialog.FileName);
}
}
private void OpenStatusTxt(string strFileFullPath)
{
StreamReader sr = new StreamReader(strFileFullPath, Encoding.Default);
txtStatus.Text = sr.ReadToEnd();//给编辑器添加内容
sr.Close();
}
private void cmsGenStatusCode_Click(object sender, EventArgs e)
{
FolderBrowserDialog dialog = new FolderBrowserDialog();
dialog.Description = "请选择文件夹";
if (dialog.ShowDialog() == DialogResult.OK)
{
if (string.IsNullOrEmpty(dialog.SelectedPath))
{
MessageBox.Show(this, "文件夹路径不能为空", "提示");
return;
}
strFileFolderPath = dialog.SelectedPath;
writeStateCode();
}
}
private static string strFileFolderPath = "";
private static string strNamespace = "";
private void writeStateCode()
{
//写入文件操作
string strBackupsFileName = strFileFolderPath + "\\生成数据备份" +
DateTime.Now.Year.ToString() + DateTime.Now.Month.ToString() + DateTime.Now.Day.ToString() +
DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString() +
DateTime.Now.Millisecond.ToString() + ".txt";
TextWriter twBackups = new StreamWriter(new BufferedStream(new FileStream(strBackupsFileName ,
FileMode.Create, FileAccess.Write)),Encoding.GetEncoding("gb2312"));
twBackups.WriteLine("//" + strFileFolderPath);//每行都添加,生成数据备份
try
{
List<string> strNames = new List<string>();
strNamespace = "";
foreach (string strLines in txtStatus.Lines)
{
//当一行的字符少于3个和前面两个字符是\\时跳过
if (strLines.Length < 3 || strLines.Substring(0, 2).Equals("//"))
continue;
if (strLines.Contains("namespace"))
{
strNamespace = strLines;//记录命名空间
twBackups.WriteLine(strLines);//每行都添加,生成数据备份
continue;
}
else if (strNamespace.Length <= 0)
{
continue;//当命名空间没有读到,则跳过
}
string[] strLine = strLines.Split(',');
writeStateCodeClass(strLine);//生成一个类文件
twBackups.WriteLine(strLines);//每行都添加,生成数据备份
strNames.Add(strLine[0]);//添加类名,为后面生成主控件添加的数据
}
if (strNamespace.Length <= 0)
{
MessageBox.Show("命名空间未找到,请参考示例代码来生成");
return;
}
twBackups.WriteLine("//>>>>>>在放该自定义控件的界面上增加listDataAdd(例如以下)才能实时刷新状态>>>>>>>>");
foreach (string item in strNames)
{
twBackups.WriteLine("//控件Name.listDataAdd(\""+ strNamespace.Replace("namespace ", "") + "." + item + "\");");
}
twBackups.Flush();
}
finally
{
twBackups.Close();
}
//使用记事本打开文档
System.Diagnostics.Process.Start("notepad.exe", strBackupsFileName);
}
/// <summary>
/// 解析一个工位的状态
/// </summary>
/// <param name="tw">命名空间</param>
/// <param name="strLine">类名称和状态名数组</param>
private void writeStateCodeClass( string[] strLine)
{
if (strLine.Length < 3) return;
TextWriter tw = new StreamWriter(new BufferedStream(new FileStream(
strFileFolderPath + "\\"+ strLine[0] + ".cs", FileMode.Create, FileAccess.Write)),
Encoding.GetEncoding("gb2312"));
tw.WriteLine(strNamespace);//设置命名空间
tw.WriteLine("{");
tw.WriteLine("\tpublic partial class " + strLine[0]);
tw.WriteLine("\t{");
tw.WriteLine("\t\t#region " + strLine[0] + "状态");
//写入枚举
tw.WriteLine("\t\tpublic enum S");
tw.WriteLine("\t\t{");
foreach (string item in strLine)
{
if (item.Equals(strLine[0]) || item.Length < 3) continue;//跳过第一个值,少于两个字符也跳过
tw.WriteLine("\t\t\t" + item + ",");
}
tw.WriteLine("\t\t}");
//写入属性
tw.WriteLine("\t\tprivate static S s状态;");
tw.WriteLine("\t\tpublic static S " + strLine[0] + "状态");
tw.WriteLine("\t\t{");
tw.WriteLine("\t\t\tget { return s状态; }");
tw.WriteLine("\t\t}");
tw.WriteLine("\t\t#region 判断当前的状态");
//做判断用
foreach (string item in strLine)
{
if (item.Equals(strLine[0]) || item.Length < 3) continue;//跳过第一个值,少于两个字符也跳过
tw.WriteLine("\t\tpublic static bool s" + GetSpellCode(item) + "_" + item);
tw.WriteLine("\t\t{");
tw.WriteLine("\t\t\tget");
tw.WriteLine("\t\t\t\t{");
tw.WriteLine("\t\t\t\tif (s状态 == S." + item + ")");
tw.WriteLine("\t\t\t\t\treturn true;");
tw.WriteLine("\t\t\t\treturn false;");
tw.WriteLine("\t\t\t\t}");
tw.WriteLine("\t\t}");
}
tw.WriteLine("\t\t#endregion");
tw.WriteLine("\t\t#region 更新状态");
//做设置用
foreach (string item in strLine)
{
if (item.Equals(strLine[0]) || item.Length < 3) continue;//跳过第一个值,少于两个字符也跳过
tw.WriteLine("\t\tprivate static void u" + GetSpellCode(item) + "_" + item + "()");
tw.WriteLine("\t\t{");
tw.WriteLine("\t\t\ts状态 = S." + item + ";");
tw.WriteLine("\t\t}");
}
tw.WriteLine("\t\t#endregion");
tw.WriteLine("\t\t#endregion");
tw.WriteLine("\t}");
tw.WriteLine("}");
//生成文件
tw.Flush();
tw.Close();
}
#endregion
#region 状态刷新
private void timeRefresh_Tick(object sender, EventArgs e)
{
if (listData.Count > 0)
RefreshData();
}
private int iOneInit = 0;//是否是第一次读取数据
public void RefreshData()
{
if (isGenCode)//是增加代码的状态不能刷新数据,因为dgvAutoStatus没有加载
return;
if (iOneInit == 0)
{
DataTable dt = new DataTable();
dt.Columns.Add("名称", Type.GetType("System.String"));
dt.Columns.Add("状态", Type.GetType("System.String"));
foreach (string item in listData)
{
DataRow mRow = dt.NewRow();
Type type = Type.GetType(item);
string[] items = item.Split('.');
object oValue = type.GetProperty(items[1] + "状态").GetValue(type, null);
mRow[0] = items[1] + "的状态";//状态名称赋值
mRow[1] = oValue.ToString();
dt.Rows.Add(mRow);
}
dgvAutoStatus.DataSource = dt;
dgvAutoStatus.ClearSelection();
dgvAutoStatus.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
dgvAutoStatus.Columns[1].FillWeight = 120;
iOneInit = 1;
//禁止排序
foreach (DataGridViewColumn item in dgvAutoStatus.Columns)
{
item.SortMode = DataGridViewColumnSortMode.NotSortable;
}
}
else
{
if (dgvAutoStatus == null || dgvAutoStatus.Rows.Count == 0) return;
int i = 0;//列表匹配和更新用的下标
foreach (string item in listData)
{
Type type = Type.GetType(item);
string[] items = item.Split('.');
object oValue = type.GetProperty(items[1] + "状态").GetValue(type, null);
//当前的状态和显示的状态不一致才更新状态
if (!(oValue.ToString()).Equals(dgvAutoStatus.Rows[i].Cells[1].Value.ToString()))
dgvAutoStatus.Rows[i].Cells[1].Value = oValue.ToString();
i++;
}
}
}
#endregion
#region 状态列表添加刷新的内容
/// <summary>
/// 状态列表添加刷新的内容
/// </summary>
/// <param name="name"></param>
public void listDataAdd(string name)
{
listData.Add(name);
}
#endregion
#region 获取汉字首字母
/// <summary>
/// 获取汉字首字母
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string GetSpellCode(string str)
{
string strTemp = "";
for (int i = 0; i < str.Length; i++)
{
if (i == 0)
strTemp += GetCharSpellCode(str.Substring(i, 1)).ToUpper();
else
strTemp += GetCharSpellCode(str.Substring(i, 1));
if (strTemp.Length >= 6)
return strTemp;
}
return strTemp;
}
/// <summary>
/// 获取1个汉字的首字母
/// </summary>
/// <param name="strChar">一个字符</param>
/// <returns></returns>
public static string GetCharSpellCode(string strChar)
{
long iChar;
byte[] ZW = System.Text.Encoding.Default.GetBytes(strChar);
if (ZW.Length == 1)
return strChar.ToLower();
else
iChar = ((short)ZW[0]) * 256 + (short)ZW[1];
if (iChar >= 45217 && iChar <= 45252)
return "a";
else if (iChar >= 45253 && iChar <= 45760)
return "b";
else if (iChar >= 45761 && iChar <= 46317)
return "c";
else if (iChar >= 46318 && iChar <= 46825)
return "d";
else if (iChar >= 46826 && iChar <= 47009)
return "e";
else if (iChar >= 47010 && iChar <= 47296)
return "f";
else if (iChar >= 47297 && iChar <= 47613)
return "g";
else if (iChar >= 47614 && iChar <= 48118)
return "h";
else if (iChar >= 48119 && iChar <= 49061)
return "j";
else if (iChar >= 49062 && iChar <= 49323)
return "k";
else if (iChar >= 49324 && iChar <= 49895)
return "l";
else if (iChar >= 49896 && iChar <= 50370)
return "m";
else if (iChar >= 50371 && iChar <= 50613)
return "n";
else if (iChar >= 50614 && iChar <= 50621)
return "o";
else if (iChar >= 50622 && iChar <= 50905)
return "p";
else if (iChar >= 50906 && iChar <= 51386)
return "q";
else if (iChar >= 51387 && iChar <= 51445)
return "r";
else if (iChar >= 51446 && iChar <= 52217)
return "s";
else if (iChar >= 52218 && iChar <= 52697)
return "t";
else if (iChar >= 52698 && iChar <= 52979)
return "w";
else if (iChar >= 52980 && iChar <= 53688)
return "x";
else if (iChar >= 53689 && iChar <= 54480)
return "y";
else if (iChar >= 54481 && iChar <= 55289)
return "z";
return strChar;
}
#endregion
}
}
以上为自定义控件的代码,在使用时还相应在自定义控件所在的页面的“Load”里面增加:
//>>>>>>在放该自定义控件的界面上增加listDataAdd(例如以下)才能实时刷新状态>>>>>>>>
//控件Name.listDataAdd("点菜");
//控件Name.listDataAdd("做菜");
//控件Name.listDataAdd("上菜");
这样自定义控件的:
private bool isGenCode = false;//是否是生成代码状态,不是生成代码状态则为列表实时显示状态
即:isGenCode = false;就可以在状态改变后列表实时显示
界面预览。
生成代码状态时界面截图(isGenCode = true):
实时显示流程对应的状态界面截图(isGenCode = false):
这样把每个线程都创建好相应的状态,在相应的位置设置好相应状态。这样既能做看多线程运行到那个位置,还能直接拿这个状态来做线程间通讯。
在没写这篇之前,觉得这种方式可以解决70%代码不好梳理的痛点。写完后发现似乎不到60%,若你有更好的办法,欢迎评论。