You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

.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

火山引擎 最新活动