在锁中调用方法是不好的做法吗? [关闭]

Posted

技术标签:

【中文标题】在锁中调用方法是不好的做法吗? [关闭]【英文标题】:Is it Bad Practice to Call Methods in Locks? [closed] 【发布时间】:2020-07-26 17:58:25 【问题描述】:

我在我的代码中使用了一个后台工作程序,它在完成工作后调用多个函数。

在这些函数中,有一些私有变量也被后台工作人员使用(尽管它们是在工作完成后访问的)。

例如,

        private void Work_Completed(object sender, RunWorkerCompletedEventArgs e)
        
            Handle_OnProgressBarCompleted();
            ParseDataFromXml();
            AddTabsWithDataGridViewToTabControl();
        

在大多数这些函数中,都有一个锁来保护私有变量不被另一个线程访问。例如,在 ParseDataFromXml()

        private void ParseDataFromXml()
        
            lock (_lock)
            
                RiskLimitsConfigColl = _riskLimitXmlReader.GetRiskLimitsConfigurations(_filePath);
                var riskLimitValidationData = new RiskLimitValidationData(RiskLimitsConfigColl);
                Dataset = riskLimitValidationData.GetData();
                RiskLimitsConfigColl = riskLimitValidationData.GetRiskLimits();
                CommonConfigurations();
            
        

我用来保护我的私有变量的锁的数量正在变得不守规矩,我觉得我在我的代码周围喷洒锁而不知道它们应该在哪里(每个私有变量都在一个 lock 语句中)。

我的问题是:在 lock 语句中包含另一个函数调用是不好的做法吗?如果ParseDataFromXML 中的CommonConfigurations 里面有一个lock 语句,我会死锁吗?使用锁定语句时,我最好的前进道路应该是什么?

我读到here那个

排他锁用于确保一次只有一个线程可以进入特定的代码段。

所以我假设我需要查找两个线程都引用的函数并在那里使用锁。

【问题讨论】:

【参考方案1】:

一般来说,你不能说“它是好的”或“它是坏的”。在某些特定的场景和案例中,同样的逻辑可能会被认为是“不好的做法”或“最佳做法”。

Microsoft 声明 lock 关键字可确保一个线程不进入代码的临界区,而另一个线程处于临界区。如果另一个线程试图输入一个锁定的代码,它会等待、阻塞,直到对象被释放。

但总的来说,不是特定于您的情况,通常在您使用锁时会出现线程问题..

基于锁的资源保护和线程/进程同步有很多缺点,其中一些是:

它们会导致阻塞,这意味着某些线程/进程必须等待 直到一个锁(或一整套锁)被释放。 锁处理增加了每次访问资源的开销,即使在 碰撞的机会非常少。 (然而,任何机会 这种碰撞是一种竞争条件)。 锁争用限制了可扩展性并增加了复杂性。 优先级反转高优先级线程/进程无法继续,如果 低优先级线程/进程持有公共锁。 护航。如果一个线程持有锁,所有其他线程都必须等待 由于时间片中断或页面错误而取消计划。 难以调试:与锁相关的错误与时间有关。他们 非常难以复制。

【讨论】:

【参考方案2】:

BackgroundWorker.RunWorkerCompleted 事件在 UI 线程中运行,因此您可能不需要锁定任何内容。只需确保将后台操作的结果设置为DoWorkEventArgs.Result 属性,然后在UI 线程中从RunWorkerCompletedEventArgs.Result 属性接收相同的结果。换句话说,避免使用共享字段将数据直接从一个线程传递到另一个线程。

关于锁定的一般建议是正确执行或根本不执行。如果您决定共享状态必须受锁保护,那么每个对该状态的访问都必须受同一个锁的保护。任何一次读取或写入都不能不受保护。到处散布锁以使事情“更安全”,就像根本没有任何锁一样好。您的代码要么是线程安全的,要么不是。

另一个一般建议是尽可能缩短锁定时间。理想情况下,锁应该保护由不超过几十条 CPU 指令组成的操作。获取和释放锁之间的总持续时间应该以纳秒为单位。如果您经常发现自己锁定了数据库调用或其他类似的冗长操作,那么您可能做错了什么。

我个人的建议是考虑完全放弃老式的BackgroundWorker 方法,转而采用现代而强大的async-await 技术。通过使用 async-await,您可以将所有代码放在一个地方,而不是分散在各种事件处理程序中。这是一个例子:

private async void Button1_Click(object sender, EventArgs e)

    string xmlPath = GetPath();
    Task<XmlDocument> task = Task.Run(() =>
    
        // Start loading the document in a background thread
        var doc = new XmlDocument();
        doc.Load(xmlPath);
        return doc;
    );

    XmlDocument xmlDoc = await task; // Wait for the completion without blocking the UI

    // We are back in the UI thread again
    Handle_OnProgressBarCompleted();
    var data = ParseDataFromXml(xmlDoc);
    AddTabsWithDataGridViewToTabControl(data);

要报告异步操作的进度,请查看here。

【讨论】:

以上是关于在锁中调用方法是不好的做法吗? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

使用没有花括号的 if 语句是一种不好的做法吗? [关闭]

静态局部变量是不好的做法吗?

在微服务 REST api 调用中返回对象列表是不好的做法吗?

使用带有 CompletableFuture 的默认通用 fork/join 池进行长时间阻塞调用是不好的做法吗?

在构造函数中传递控制器总是不好的做法吗?

为啥从代码中调用事件处理程序是不好的做法?