【问题标题】: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那个

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

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

【问题讨论】:

    标签: c# multithreading locking backgroundworker


    【解决方案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

      【讨论】:

        猜你喜欢
        • 2015-01-22
        • 2020-08-18
        • 2016-08-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多