【问题标题】:How do I avoid two (or more) threads that work on a table at the same time to not work on same row?如何避免同时在表上工作的两个(或更多)线程不在同一行上工作?
【发布时间】:2017-10-20 08:22:50
【问题描述】:

我正在尝试制作一个 C# WinForms 应用程序,该应用程序从保存在名为“链接”的表中的 url 获取数据。每个链接都有一个“最后检查”和“下一次检查”日期时间,并且有一个“间隔”根据最后一次检查决定“下一次检查”。

现在,我正在做的是在进行网络抓取之前使用查询获取 ID,然后我将 Last Checked 转换为 DateTime.Now 并将 Next Check 转换为 null,直到所有操作完成。在网络抓取完成后,两者都会得到更新。

问题是如果正在进行的进程有任何“中止”,lastcheck 将是一个日期,但 nextcheck 将为空。

所以我需要一个更好的方法让两个进程不能在同一张表的同一行上工作。但不确定如何。

【问题讨论】:

  • 添加您正在使用的 DBMS 的标签
  • 抱歉,添加了!
  • 你是否使用多个线程进行抓取?
  • 技术上我现在不是,但我正在尝试安排它。我创建了一个“WebScrape”类,该类基于“LINKS”表中的链接获取带有 HTML-Agility-Pack 的 HTML 文档,这些链接具有“NextCheck”时间。我有一个函数来创建此类的新实例,其中包含“numberOfText”或“ImageSize”之类的值,这些值稍后会根据 LinkID(LINKS 表上的 ID)添加到 PROCESSES 表中,并更新 lastcheck 和 nextcheck 时间链接。所以我打算为每个链接设置 N 个调用这些类的函数。
  • 问题是你最终同时做 2 件事 - 即 2 个相同的请求/抓取?还是如果刮擦失败,它会在数据库中将下一个检查设置为空?还是两个问题?

标签: c# sql sql-server multithreading


【解决方案1】:

对于多线程解决方案,标准的工程方法是使用工作池和工作池。

这只是一个概念草图 - 您应该根据自己的情况进行调整:

  • 工作人员(即线程)查看工作池。如果有一些可用的工作,它会将其标记为in_progress。必须这样做,以便没有两个线程可以进行相同的工作。例如,您可以在 C# 中使用 lock 在数据库中进行查询,并在返回之前标记一行。
  • 您需要有一种方法可以在线程结束后取消标记它。成功与否,in_progress 必须重新设置。通常,您可以使用 finally 块,以便在发生任何异常时不会错过它。
  • 如果没有可用的工作,线程进入睡眠状态。
  • 每当有新工作到达时(即INSERT,或nextcheck 到期),就会唤醒一个休眠线程。
  • 当您的程序启动时,它应该清除所有 in_progress 标志,以防之前发生崩溃。
  • 您应该利用 DBMS 事务,以便工作人员在完成其工作后所做的任何更改都是原子的 - 即其他线程会立即感知它们。

通过改变worker pool的大小,可以设置同时活跃worker的最大数量。

【讨论】:

  • 我找到了另一个解决方案,看起来与您的建议相似。首先,我根据我需要的条件(如果是 NextCheck 的时间)选择一组 ID,并将每个 ID 分配给不同的函数,用于循环。问题似乎是因为我每次都选择一个单一的 ID,所以在开始时选择一组 ID 可能会更聪明。
  • 这是一个生产者/消费者模式(工作池 - 生产者,工人 - 消费者) . BlockingCollection 是该模式的 .Net 实现。
【解决方案2】:

首先,控制器/工作人员的分离可能是其他答案中提到的更好的模式。如果线程数量变大并且要检查的链接数量很大,这将更好地工作。

但如果你的问题是这样的:

但它的问题是,如果由于任何原因抓取得到 中止/中途完成/无法正常工作,LastCheck 变为 DateTime.Now 但 NextCheck 保留为 NULL,并且以前 LastCheck/NextCheck 值消失了,LastCheck/NextCheck 值消失了 为未实际检查的链接更新

您只需要更好地处理错误。

失败将导致异常。捕获异常并通过重置数据库中的状态来处理它。例如:

void DoScraping(.....)
{
    try
    {
        // ....
    }
    catch (Exception err)
    {
        // oh dear, it went wrong, reset lastcheck/nextcheck
    }
}

您将上次/下次检查重置为什么取决于您。如果当您确定“下一步要做的事情”时,您还可以将它们重置为开始时的位置,您还可以获取 last/nextcheck 的值并存储在变量中。然后在失败的情况下只需设置它们之前的状态。

【讨论】:

    猜你喜欢
    • 2012-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-29
    • 1970-01-01
    • 1970-01-01
    • 2018-03-09
    相关资源
    最近更新 更多