You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# 如何为电梯控制程序添加委托与BackgroundWorker,保留timer并解决跨线程数据库报错

委托改造实现方案

原有代码中电梯上下行、开关门逻辑重复度很高,用委托封装后可以大幅降低后续扩展的维护成本,改造步骤如下:

  1. 在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;
  1. 在构造函数中绑定委托和原有逻辑
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卡顿,同时解决跨线程操作问题:

  1. 先声明必要的字段
// 电梯调度后台Worker
private BackgroundWorker liftScheduler = new BackgroundWorker();
// 按钮请求队列,支持多按钮同时点击排队
private Queue<LiftOperation> requestQueue = new Queue<LiftOperation>();
// 队列操作锁,避免并发冲突
private object queueLock = new object();
  1. 构造函数中配置BackgroundWorker
public Form1()
{
    InitializeComponent();
    // ... 上面的委托绑定代码
    
    liftScheduler.WorkerReportsProgress = true;
    liftScheduler.WorkerSupportsCancellation = true;
    liftScheduler.DoWork += LiftScheduler_DoWork;
    liftScheduler.ProgressChanged += LiftScheduler_ProgressChanged;
}
  1. 后台线程仅处理队列调度,不碰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;
}
  1. 主线程执行实际电梯逻辑,无跨线程问题
private void LiftScheduler_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    var action = e.UserState as LiftOperation;
    action?.Invoke();
}
  1. 修改按钮点击事件,将请求加入队列即可,例如G楼下行按钮:
private void btng_Click(object sender, EventArgs e)
{
    btng.BackColor = Color.DarkOrange;
    lock (queueLock)
    {
        requestQueue.Enqueue(GoDownAction);
    }
    // 首次点击启动调度器
    if (!liftScheduler.IsBusy)
    {
        liftScheduler.RunWorkerAsync();
    }
}
  1. 窗口关闭时停止后台线程,避免资源泄漏
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 14:24:02