WINCE项目上线,可以喘口气,可以来优化一下后台系统关键的操作。
B2C后台系统订单流程的几个关键点,下单,开始拣货,发货,这次就从开始拣货改起。
开始拣货由于涉及表众多,而且涉及到多个仓库的高并发,所以其中数据控制尤为重要。在之前的方法中,我们加了很多重限制,比如Serializable隔离级别的事务,对Nhibernate读取出来的关键对象加写锁防止脏读。最后改到批量处理时客户端服务器一次提交一单防止跨多个Service产生的脏数据。这样改来数据的安全性有了保障,但是性能是有些问题的。不过这个功能性能需求远远要下雨数据完整性的需求。
之前的客户端采用的是单线程方式,并且操作方式不甚友好,选中需要打印的订单开始拣货后,整个页面是无法响应的,也就是这个客户端程序基本上是不能做其它动作的,并且拣货结果也要等一次提交完成之后才能反馈,如果能改成自动的,自动查询自动提交,这样肯定会节省很多人力。
所以综合以上需求,我们需要完成如下功能,
1.一个自动实现查询,提交这样功能的客户端程序。
2.必须要异步,这样可以在这个操作进行的时候程序可以进行其他的功能。
3.必须要中途可以实时汇报进度,输出反馈。
4.这个提交可能涉及到的数据条数百条级别,所以要随时可以中途停止。
这样的需求,微软提供给我们的BackgroundWorker既可以完美实现
MSDN地址: http://msdn.microsoft.com/zh-cn/library/system.componentmodel.backgroundworker%28v=vs.80%29.aspx
UI设计如下
可以看见,上面是订单过滤的查询条,左侧是将要操作的数据,右侧是操作的输出结果,下面是操作方式以及操作进度条
好吧,界面搞定了,我们开始编码
首先生命一个BackgroundWorker,
private System.ComponentModel.BackgroundWorker backgroundWorkerPick;
因为要在运行时报告进度,还要随时可以停止,所以修改属性
this.backgroundWorkerPick.WorkerReportsProgress = true;
this.backgroundWorkerPick.WorkerSupportsCancellation = true;
然后加上加上Do_work,complete,processing的委托
this.backgroundWorkerPick.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorkerPick_DoWork);
this.backgroundWorkerPick.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorkerPick_ProgressChanged);
this.backgroundWorkerPick.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorkerPick_RunWorkerCompleted);
//
BackgroundWorker属性设置完成,下面实现3个事件
private void backgroundWorkerPick_DoWork(object sender, DoWorkEventArgs e)
{
CleanListBox();
if (_controller._pendingOrders.Count == 0)
{
MessageBox.Show("无可用订单");
return;
}
var OrderCount = _controller._pendingOrders.Count;
foreach (var order in _controller._pendingOrders)
{
if (backgroundWorkerPick.CancellationPending)
break;
//这里做事务提交,调用service操作
var result = _controller.PickOrder(order, _controller.WareshouseId);
_controller._handledOrders.Add(order);
if (result.Result == 0)
_controller._successOrders.Add(order);
else
{
_controller._failedOrders.Add(order);
}
int percent =(int)((_controller._handledOrders.Count / (float)OrderCount) * 100);
//将结果传入Progress事件
backgroundWorkerPick.ReportProgress(percent, result);
}
}
private void backgroundWorkerPick_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
DoSearch(false, false);
//如果是勾选了轮询操作,则做下一次Do_work
if (isLootPick)
backgroundWorkerPick.RunWorkerAsync();
else
EnableControls(true);
}
private void backgroundWorkerPick_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var i = e.ProgressPercentage;
//调用委托去更新状态条
ShowProgress(i);
lbPickResult.Text = string.Format("拣货结果:共操作{0}个,成功{1}个,失败{2}个",
_controller._handledOrders.Count, _controller._successOrders.Count,
_controller._failedOrders.Count);
SvcResult result = (SvcResult)e.UserState;
//输出结果到LISTBOX
if (result.Result != 0)
lvResult.Items.Add(result.ErrorMessage.Aggregate(string.Concat));
Application.DoEvents();
}
开始自动拣货跟结束自动拣货BUTTON的事件
private void buttonCreatePicks_Click(object sender, EventArgs e)
{
if (!backgroundWorkerPick.IsBusy)
{
DoSearch(false, true);
EnableControls(false);
prog.Value = 0;
//lbPickResult.Text = "";
backgroundWorkerPick.RunWorkerAsync();
}
}
private void btnEnd_Click(object sender, EventArgs e)
{
if (backgroundWorkerPick.IsBusy)
{
backgroundWorkerPick.CancelAsync();
EnableControls(true);
checkBox1.Checked = false;
isLootPick = false;
}
}
设置ProgressBar,跟实时输入信息的Lable信息,还有清空ListBox控件信息。
由于是子线程中涉及到父窗体控件的设置,所以使用委托来处理
private delegate void ShowProgressCallBack(int percent);
private void ShowProgress(int percent)
{
if (prog.InvokeRequired)
{
var callback = new ShowProgressCallBack(ShowProgress);
Invoke(callback, new object[] { percent });
return;
}
prog.Value = percent;
}
private delegate void CleanListBoxCallBack();
private void CleanListBox()
{
if (lvResult.InvokeRequired)
{
var callback = new CleanListBoxCallBack(CleanListBox);
Invoke(callback);
return;
}
lvResult.Items.Clear();
}
自动拣货CHECKBOX属性设置,可以注意到我在backgrondWorker_Complete事件里面使用到了这个属性,来控制是否进行下一轮操作
private bool isLootPick = false;
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
isLootPick = checkBox1.Checked;
}
这个listBox_Keypress事件是用来复制Listbox中信息的方法
private void listBoxFileList_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
{
if (e.KeyChar == 3) //Crtl+C
{
//Clipboard.SetDataObject(lvResult.SelectedItem.ToString());
//Clipboard.SetDataObject(lvResult.SelectedItems.Trim());
string val = "";
for (int i = 0; i < lvResult.SelectedItems.Count; i++)
{
val += lvResult.SelectedItems[i].ToString() + "\r\n";
}
Clipboard.SetDataObject(val);
}
}
最后一个方法,是查询要处理的数据集方法,其中涉及到了一些控件状态的设置,利用default参数控制
private void DoSearch(bool justForQuery = false, bool needRefeshLable = false) { EnableControls(false); //非循环开始拣货时刷新LABLE if (needRefeshLable) { lbPickResult.Text = "查询订单中"; Application.DoEvents(); } _controller._dto.OrderType = (int)cbOrderType.SelectedValue; _controller._dto.OrderStartDate = dtpOrderDate.Value; _controller._dto.OrderEndDate = dtpOrderEndDate.Value; if (cbWareHouse.SelectedItem != null) { _controller._dto.WareHouseId = (long)cbWareHouse.SelectedValue; } _controller.DoSearchEvent(); dgvResult.DataSource = null; lbSearchResult.Text = string.Format("查询结果:所有可操作订单数{0}个", _controller._pendingOrders.Count); dgvResult.DataSource = _controller._pendingOrders; _controller.WareshouseId = (long)cbWareHouse.SelectedValue; SetDataGridViewStyle(dgvResult); //单查询刷新LABLE,以及控件状态 if (justForQuery) { EnableControls(true); } //非循环开始拣货时刷新LABLE if (needRefeshLable) { lbPickResult.Text = "查询完成"; Application.DoEvents(); } }
OK,工作基本完成,需求基本全部实现。功能就是利用backgroundworker实现了客户端的异步多线程操作,其中的方法细节基本可以涵盖此控件的一般使用。