.NET Framework 4.8异步SQL任务队列的线程安全与UI适配问题
.NET Framework 4.8下线程安全改造与UI线程事件处理方案
一、线程安全改造
1. 数据存储(List)的线程安全访问
直接用lock包裹所有对List<T>的读写操作,是最直接的线程安全保障方式,确保同一时间只有一个线程能操作集合:
// 定义私有只读锁对象,避免外部修改 private readonly object _listLock = new object(); private List<T> _dataList = new List<T>(); // 读取操作示例 public T GetItem(int index) { lock (_listLock) { return _dataList[index]; } } // 更新操作示例 public void UpdateItem(T updatedItem) { lock (_listLock) { var targetIndex = _dataList.FindIndex(item => item.Id == updatedItem.Id); if (targetIndex != -1) { _dataList[targetIndex] = updatedItem; } } }
如果是读多写少的场景,用ReaderWriterLockSlim优化并发效率——允许多个线程同时读,写操作独占:
private readonly ReaderWriterLockSlim _listLock = new ReaderWriterLockSlim(); // 读取操作 public T GetItem(int index) { _listLock.EnterReadLock(); try { return _dataList[index]; } finally { _listLock.ExitReadLock(); } } // 更新操作 public void UpdateItem(T updatedItem) { _listLock.EnterWriteLock(); try { var targetIndex = _dataList.FindIndex(item => item.Id == updatedItem.Id); if (targetIndex != -1) { _dataList[targetIndex] = updatedItem; } } finally { _listLock.ExitWriteLock(); } }
2. 后台任务队列的线程安全实现
因为需要支持将指定对象移到队列顶部,ConcurrentQueue无法直接满足,用lock包裹普通Queue<T>的所有操作即可:
private readonly object _queueLock = new object(); private Queue<T> _taskQueue = new Queue<T>(); // 添加任务到队列 public void EnqueueTask(T item) { lock (_queueLock) { _taskQueue.Enqueue(item); } } // 将指定对象移到队列顶部优先处理 public void PrioritizeItem(T item) { lock (_queueLock) { var tempList = _taskQueue.ToList(); if (tempList.Remove(item)) { tempList.Insert(0, item); _taskQueue = new Queue<T>(tempList); } } } // 取出队首任务 public bool TryDequeue(out T item) { lock (_queueLock) { return _taskQueue.TryDequeue(out item); } }
二、让Address_Loaded事件在UI线程执行
根据你使用的UI框架,选择对应的线程切换方式:
1. WinForms场景
触发事件前判断当前线程是否为UI线程,若不是则通过Control.Invoke切换:
// 假设当前类是WinForms控件/窗体,自带Invoke属性 private void OnAddressLoaded(T loadedItem) { if (this.InvokeRequired) { // 切换到UI线程执行 this.Invoke(new Action<T>(OnAddressLoaded), loadedItem); return; } // 此处逻辑已在UI线程执行 Address_Loaded?.Invoke(this, new AddressLoadedEventArgs(loadedItem)); }
2. WPF场景
使用Dispatcher切换到UI线程:
private void OnAddressLoaded(T loadedItem) { if (!Dispatcher.CheckAccess()) { // 异步切换到UI线程(需同步用Dispatcher.Invoke) Dispatcher.BeginInvoke(new Action<T>(OnAddressLoaded), loadedItem); return; } // 事件逻辑已在UI线程执行 Address_Loaded?.Invoke(this, new AddressLoadedEventArgs(loadedItem)); }
额外注意事项
- 后台执行SQL查询时,推荐用
Task.Run结合async/await实现异步逻辑,比直接创建Thread更易维护。 - 若任务队列需要持续处理,可配合
ManualResetEventSlim实现等待唤醒,避免空轮询浪费资源。
内容的提问来源于stack exchange,提问作者keco




