线程写问题

时间:2010-09-10 13:38:28

标签: c# multithreading

我有一个问题。我已准备好许多关于最佳主题的网页,例如http://www.albahari.com/threading/part4.aspx

一切都写得很好,但我仍然有线程问题。我同时运行6个线程。我正在解析一些数据,这些数据必须存储到数据库中。但我不能两次存储相同的数据。

现在我在数据库中获得了许多复制数据。我怎么能阻止这个。我认为lock()并不好。我想使用Monitor,但我不知道这是否正常。

这是线程代码:

CultureInfo contentCulture = (CultureInfo)propertyBag["LanguageCulture"].Value;
                string cultureDisplayValue = "N/A";
                if (!contentCulture.IsNull())
                {
                    cultureDisplayValue = contentCulture.DisplayName;
                }

                AllocConsole();

                Console.Out.WriteLine();
                Console.Out.WriteLine("Url: {0}", propertyBag.Step.Uri);
                Console.Out.WriteLine("Content type: {0}", propertyBag.ContentType);
                Console.Out.WriteLine("Content length: {0}", propertyBag.Text.IsNull() ? 0 : propertyBag.Text.Length);
                Console.Out.WriteLine("Depth: {0}", propertyBag.Step.Depth);
                Console.Out.WriteLine("Culture: {0}", cultureDisplayValue);
                Console.Out.WriteLine("ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
                Console.Out.WriteLine("Thread Count: {0}", crawler.ThreadsInUse);
                Console.Out.WriteLine();

                ConsoleCount++;

                if (ConsoleCount > 1000)
                {
                    Console.Clear();
                    ConsoleCount = 0;
                }

                HtmlDocument htmlDoc = new HtmlDocument();
                Encoding documentEncoding = htmlDoc.DetectEncoding(propertyBag.GetResponse());
                propertyBag.GetResponse().Seek(0, SeekOrigin.Begin);

                if (documentEncoding != null)
                {
                    htmlDoc.Load(propertyBag.GetResponse(), documentEncoding, true);
                }
                else
                {
                    htmlDoc.Load(propertyBag.GetResponse(), true);
                }

                string htmlContent = htmlDoc.DocumentNode.OuterHtml;
                if (string.IsNullOrEmpty(htmlContent)) return;

                IAdvertismentsDao advertismentsDao = DaoFactory.GetAdvertisementsDao();
                List<TagValuePair> listTagValuePair = HtmlHelper.GetTagsAndValues(htmlContent);
                string link = propertyBag.Step.Uri.ToString();

                if (string.IsNullOrEmpty(link))
                {
                    link = propertyBag.ResponseUri.ToString();
                }


                Advertisements ad =
                    new CrawlerManager(DaoFactory, ConnectionString).GetAdvertismentFromHtmlContent(
                        listTagValuePair, Agency, link);

                if (ad != null)
                {
                    if (!advertismentsDao.AdvertisementUrlExist(ad.Url))
                    {
                        if (
                            !advertismentsDao.AdvertisementExist(ad.Price, ad.HollidayDuration, ad.Name,
                                                                 ad.Description, ad.City, ad.Area, ad.Country,
                                                                 ad.Agency))
                        {
                            advertismentsDao.Save(ad);
                            advertismentsDao.CommitChanges();
                        }
                    }
                    else
                    {
                        if (advertismentsDao.ChekIfNeedUpdate(ad))
                        {
                            Advertisements advertisements = advertismentsDao.GetByUrl(ad.Url);

                            advertisements.Price = ad.Price;
                            advertisements.HollidayDuration = ad.HollidayDuration;
                            advertisements.Name = ad.Name;
                            advertisements.Description = ad.Description;
                            advertisements.DepartureDate = ad.DepartureDate;

                            advertismentsDao.SaveOrUpdate(advertisements);
                            advertismentsDao.CommitChanges();
                        }
                    }

                    InvokeEvent(ad, string.Empty);
                }
                else
                    InvokeEvent(null, link);

4 个答案:

答案 0 :(得分:1)

问题是您没有正确分割要解析的数据。你说你有六个线程正在解析数据,但其中一些现在正在解析相同的数据,显然。

查看您的代码,我认为问题是您的propertyBag。我不确定那是什么,但我认为它不会给每个线程分析正确的数据。您可能需要查看ConcurrentQueue课程以获得一些想法。

答案 1 :(得分:1)

我猜你有问题:

if (!advertismentsDao.AdvertisementUrlExist(ad.Url))
{
    if (
        !advertismentsDao.AdvertisementExist(ad.Price, ad.HollidayDuration, ad.Name,
                                             ad.Description, ad.City, ad.Area, ad.Country,
                                             ad.Agency))
    {
       advertismentsDao.Save(ad);
       advertismentsDao.CommitChanges();
    }
}

线程#1似乎完全有可能看到Url和Advertisement不存在 - 然后被线程#2强加。线程#2将看到Url和Advertisement不存在,然后两个线程都将尝试保存。

关于如何解决它的几点想法:

  1. 拆分您的输入,以便没有2个线程在同一个广告上工作
  2. 将“if exists”逻辑移动到数据库中,并利用行锁等
  3. 锁定整个“if exists,then save”部分。
  4. 使用基于ad.GetHashCode()的互斥锁来确保一次只有一个线程正在处理类似的广告。您可以从AdvertisementUrlExist调用中检索此互斥锁 - 然后阻止它直到它可用。当然,你基本上是在那时实现行级锁定。
  5. 完全删除if exists个检查,然后将数据写入数据库。您可以通过SELECT每晚或按需聚合等。

答案 2 :(得分:0)

您必须在数据上定义一个唯一的索引,即“业务密钥”,即在您的案例中,某些内容会使该行“唯一”。

如果您插入两次相同的数据,数据库将抛出异常。然后,您可以忽略此异常(已存在数据)或更新现有行(例如,计算项目出现的次数)。

答案 3 :(得分:0)

由于可以从具有所需权限的任何地方访问数据库(而不仅仅是流程中的多个线程),因此您需要将引用框架从考虑内部进程的并发性转移到数据库的所有可能用户。您的锁定需要在数据库中进行,并且数据完整性由密钥和约束强制执行。