C# 如何为电梯控制程序添加委托与BackgroundWorker,保留timer并解决跨线程数据库报错
委托改造实现方案
原有代码中电梯上下行、开关门逻辑重复度很高,用委托封装后可以大幅降低后续扩展的维护成本,改造步骤如下:
- 在
Form1类中定义通用委托类型和对应实例
// 电梯动作通用委托 public delegate void LiftOperation(); // 开关门动作委托,参数为true代表1楼,false代表G楼 public delegate void DoorOperation(bool isFirstFloor); // 声明委托实例 private LiftOperation GoUpAction; private LiftOperation GoDownAction; private DoorOperation OpenDoorAction; private DoorOperation CloseDoorAction;
- 在构造函数中绑定委托和原有逻辑
public Form1() { InitializeComponent(); // 绑定原有方法到委托 GoUpAction = going_up; GoDownAction = going_down; OpenDoorAction = (isFirst) => { if (isFirst) dooropenup(); else dooropendown(); }; CloseDoorAction = (isFirst) => { if (isFirst) doorcloseup(); else doorclosedown(); }; }
后续如果新增楼层或者修改动作逻辑,只需要修改委托绑定的目标方法即可,不需要逐个修改按钮触发逻辑。
BackgroundWorker适配方案(保留原有Timer)
原有System.Windows.Forms.Timer本身就在UI线程触发,完全可以保留不用修改,BackgroundWorker仅用来处理按钮请求调度,避免UI卡顿,同时解决跨线程操作问题:
- 先声明必要的字段
// 电梯调度后台Worker private BackgroundWorker liftScheduler = new BackgroundWorker(); // 按钮请求队列,支持多按钮同时点击排队 private Queue<LiftOperation> requestQueue = new Queue<LiftOperation>(); // 队列操作锁,避免并发冲突 private object queueLock = new object();
- 构造函数中配置BackgroundWorker
public Form1() { InitializeComponent(); // ... 上面的委托绑定代码 liftScheduler.WorkerReportsProgress = true; liftScheduler.WorkerSupportsCancellation = true; liftScheduler.DoWork += LiftScheduler_DoWork; liftScheduler.ProgressChanged += LiftScheduler_ProgressChanged; }
- 后台线程仅处理队列调度,不碰UI和数据库
private void LiftScheduler_DoWork(object sender, DoWorkEventArgs e) { var worker = sender as BackgroundWorker; while (!worker.CancellationPending) { lock (queueLock) { if (requestQueue.Count > 0) { var action = requestQueue.Dequeue(); // 通知主线程执行动作 worker.ReportProgress(0, action); } } Thread.Sleep(100); } e.Cancel = true; }
- 主线程执行实际电梯逻辑,无跨线程问题
private void LiftScheduler_ProgressChanged(object sender, ProgressChangedEventArgs e) { var action = e.UserState as LiftOperation; action?.Invoke(); }
- 修改按钮点击事件,将请求加入队列即可,例如G楼下行按钮:
private void btng_Click(object sender, EventArgs e) { btng.BackColor = Color.DarkOrange; lock (queueLock) { requestQueue.Enqueue(GoDownAction); } // 首次点击启动调度器 if (!liftScheduler.IsBusy) { liftScheduler.RunWorkerAsync(); } }
- 窗口关闭时停止后台线程,避免资源泄漏
private void Form1_FormClosing(object sender, FormClosingEventArgs e) { liftScheduler.CancelAsync(); }
其他按钮的点击事件同理,把原来直接调用going_up/going_down的逻辑改成将对应委托加入队列即可,原有Timer的所有Tick逻辑完全不需要修改。
多线程数据库写入冲突解决
你遇到的跨线程写.accdb报错,根源是Access数据库不支持多线程并发操作,且OleDb连接是线程亲和的,不能跨线程调用,修改insertdata方法即可解决:
private void insertdata(string logContent) { // 每次写入新建连接,不要用全局共享连接 string connStr = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=你的数据库路径.accdb;Persist Security Info=False;"; using (OleDbConnection conn = new OleDbConnection(connStr)) { conn.Open(); // 替换成你实际的表名和字段名 string sql = "INSERT INTO 日志表(内容字段) VALUES(@content)"; using (OleDbCommand cmd = new OleDbCommand(sql, conn)) { cmd.Parameters.AddWithValue("@content", logContent); cmd.ExecuteNonQuery(); } } }
每次调用insertdata都会新建独立连接,用完自动释放,完全避免跨线程连接冲突,不需要修改其他数据库相关逻辑。
内容的提问来源于stack exchange,提问作者lifecycles
相关产品推荐
相关产品推荐

