在锁中调用方法是不好的做法吗? [关闭]
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。
【讨论】:
以上是关于在锁中调用方法是不好的做法吗? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
在微服务 REST api 调用中返回对象列表是不好的做法吗?