System.InvalidOperationException:对象当前正在其他地方使用

发布于:2025-03-23 ⋅ 阅读:(30) ⋅ 点赞:(0)

Form窗口当UI更新被多个地方的调用,并且存在冲突时,可能出现以上错误。

比如UI正在初始化,结果另外一个线程调用了更改界面的函数,则会出现上面的错误,因为这个错误是偶发的,所以暂无办法稳定复现。

解决办法,利用加锁的方式,避免UI竞争。

在C#窗体应用中,通过以下步骤实现‌全局锁机制‌,可有效避免因多线程或异步操作导致的对象正在其他地方使用异常:
‌一、全局锁实现方案‌
‌1. 定义全局锁对象‌

在Form类中声明一个私有锁对象,用于同步所有共享资源的访问:

public partial class MainForm : Form
{
    // 全局锁对象(推荐使用引用类型)
    private readonly object _globalLock = new object();

    // 若涉及异步操作,使用SemaphoreSlim
    private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1);
}

‌2. 同步访问共享资源‌

在操作‌非UI共享资源‌(如文件、集合、数据库连接)时,使用lock或Monitor强制串行化:

private void UpdateData()
{
    lock (_globalLock)  // 进入临界区
    {
        // 操作共享资源(如List<T>、文件流等)
        sharedList.Add(newItem);
    }  // 自动释放锁
}

‌3. 异步操作中的锁管理‌

若涉及async/await,使用SemaphoreSlim代替lock:

private async Task UpdateDataAsync()
{
    await _asyncLock.WaitAsync();  // 异步等待锁
    try
    {
        // 异步操作共享资源
        await WriteToFileAsync("data.txt");
    }
    finally
    {
        _asyncLock.Release();  // 确保释放锁
    }
}

‌4. UI控件的线程安全访问‌

对UI控件的操作,需通过Invoke/BeginInvoke切换到主线程,并‌结合全局锁‌:

private void SafeUpdateUI(string text)
{
    if (label1.InvokeRequired)
    {
        BeginInvoke(new Action(() => SafeUpdateUI(text)));
        return;
    }

    lock (_globalLock)  // 保护UI操作相关资源
    {
        label1.Text = text;
        progressBar1.Value = CalculateProgress();
    }
}

‌二、关键场景与优化‌
‌1. 避免死锁‌

    ‌锁嵌套规则‌:确保所有代码路径以‌相同顺序‌获取锁。
    错误示例:

    void MethodA()
    {
        lock (lockA)
        {
            lock (lockB) { ... }  // 若其他方法先锁B再锁A,可能导致死锁
        }
    }

    ‌超时机制‌:使用Monitor.TryEnter防止无限等待:

    if (Monitor.TryEnter(_globalLock, TimeSpan.FromSeconds(5)))
    {
        try { ... }
        finally { Monitor.Exit(_globalLock); }
    }
    else
    {
        throw new TimeoutException("获取锁超时");
    }

‌2. 性能优化‌

    ‌缩小临界区‌:仅在操作共享资源时持有锁,减少锁的持有时间:

    var tempData = FetchData();  // 非临界区操作
    lock (_globalLock)
    {
        sharedList.AddRange(tempData);  // 快速操作
    }

    ‌读写分离‌:对读多写少的场景,使用ReaderWriterLockSlim:
 

    private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();

    // 读操作
    public string ReadData()
    {
        _rwLock.EnterReadLock();
        try { return sharedData.ToString(); }
        finally { _rwLock.ExitReadLock(); }
    }

    // 写操作
    public void WriteData(string value)
    {
        _rwLock.EnterWriteLock();
        try { sharedData.Append(value); }
        finally { _rwLock.ExitWriteLock(); }
    }

‌三、完整示例‌
‌场景‌:窗体启动时异步加载数据,同时允许用户手动刷新,确保两个操作不会冲突。
 

public partial class MainForm : Form
{
    private List<string> _dataCache = new List<string>();
    private readonly SemaphoreSlim _dataLock = new SemaphoreSlim(1, 1);

    public MainForm()
    {
        InitializeComponent();
        this.Load += MainForm_Load;
        btnRefresh.Click += BtnRefresh_Click;
    }

    private async void MainForm_Load(object sender, EventArgs e)
    {
        await LoadDataAsync();
    }

    private async void BtnRefresh_Click(object sender, EventArgs e)
    {
        await LoadDataAsync();
    }

    private async Task LoadDataAsync()
    {
        await _dataLock.WaitAsync();
        try
        {
            // 模拟耗时数据加载
            var newData = await Task.Run(() => FetchDataFromDatabase());
            
            // 切换到UI线程更新控件
            BeginInvoke(new Action(() =>
            {
                lock (_dataLock)  // 确保与其他UI操作互斥
                {
                    _dataCache = newData;
                    dgvData.DataSource = _dataCache;
                }
            }));
        }
        finally
        {
            _dataLock.Release();
        }
    }
}

‌四、注意事项‌

    ‌锁的作用域‌
    确保锁仅用于保护‌真正共享的资源‌,避免滥用导致性能下降。

    ‌UI线程与锁的关系‌
    Invoke/BeginInvoke本身不提供线程安全,需结合锁保护非原子操作(如先读后写)。

    ‌资源释放‌
    在窗体关闭时释放锁相关资源:

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        _asyncLock?.Dispose();
        base.OnFormClosing(e);
    }

通过上述方法,可实现窗体级别的全局线程安全,彻底解决对象正在其他地方使用异常。