【问题标题】:Multithreading issue ,Maybe a DeadLock using Foreach多线程问题,可能是使用 Foreach 的死锁
【发布时间】:2014-11-12 19:12:54
【问题描述】:

Parallel.ForEach 继续运行,我的程序没有结束。我无法追踪它在第一次迭代后的去向。我的猜测是它会陷入僵局并继续进行上下文切换。

private void ReadInputFile()
{
    var collection = new ConcurrentBag<PropertyRecord>();
    var lines = System.IO.File.ReadLines(InputFileName);
    int i = 0;
    int RecordsCount = lines.Count();
    Parallel.ForEach(lines, line =>
    {
        if (string.IsNullOrWhiteSpace(line))
        {
            return;                    
        }

        var tokens = line.Split(',');
        var postalCode = tokens[0];
        var country = tokens.Length > 1 ? tokens[1] : "england";

        SetLabelNotifyTwoText(
            string.Format(
                "Reading PostCode {0} out of {1}"
                i,
                lines.Length));

        var tempRecord = GetAllAddesses(postalCode, country);
        if (tempRecord != null)
        {
            foreach (PropertyRecord r in tempRecord)
            {
                collection.Add(r);
            }
        }    
    });
}

private List<PropertyRecord> GetAllAddesses(
        string postalCode,
        string country = "england")
{
    SetLabelNotifyText("");
    progressBar1.Value = 0;
    progressBar1.Update();

    var records = new List<PropertyRecord>();
    using (WebClient w = new WebClient())
    {
        var url = CreateUrl(postalCode, country);
        var document = w.DownloadString(url);
        var pagesCount = GetPagesCount(document);
        if (pagesCount == null)
        {
            return null;
        }

        for (int i = 0; i < pagesCount; i++)
        {
            SetLabelNotifyText(
                string.Format(
                    "Reading Page {0} out of {1}",
                    i,
                    pagesCount - 1));

            url = CreateUrl(postalcode,country, i);
            document = w.DownloadString(url);
            var collection = Regex.Matches(
                document,
                "<div class=\"soldDetails\">(.|\\n|\\r)*?class=" +
                "\"soldAddress\".*?>(?<address>.*?)(</a>|</div>)" +
                "(.|\\n|\\r)*?class=\\\"noBed\\\">(?<noBed>.*?)" +
                "</td>|</tbody>");

            foreach (var match in collection)
            {
                var r = new PropertyRecord();

                var bedroomCount = match.Groups["noBed"].Value;
                if(!string.IsNullOrEmpty(bedroomCount))
                {
                    r.BedroomCount = bedroomCount;             
                }
                else
                {
                    r.BedroomCount = "-1";
                }

                r.address = match.Groups["address"].Value;

                var line = string.Format(
                    "\"{0}\",{1}",
                    r.address
                    r.BedroomCount);
                OutputLines.Add(line);

                Records.Add(r);
            }
        }
    }

    return Records;
}

在没有Parallel.ForEach 的情况下运行良好,但需要使用Parallel.ForEach

我已经调试过了,第一次从GetAllAdresses-method 返回后,Step Next 按钮停止,它只是在后台继续调试。它不会出现在我放置的任何书签上。

【问题讨论】:

  • 从这里调试会更加困难。
  • 我已经调试过了,第一次从 GetAllAddresses(..) 返回,我不知道在哪里
  • 请使用调试器。还有SetLabelNotifyTextSetLabelNotifyTwoText 有什么作用?那叫control.Invoke吗?那个主线程是哪个线程调用的?
  • 是的,他们在那里阻止非法线程访问问题以更新 GUI
  • @Charlie 很高兴为您提供帮助,但这绝对不是我们这里的远程调试会话!请自己做你的功课并调查正在运行的任务/线程/堆栈!不太可能有答案指出困扰您的那条线,因为这个问题不符合SSCCE

标签: c# .net task-parallel-library parallel.foreach


【解决方案1】:

正如您在 cmets 中所说,您的 SetLabelNotifyTextSetLabelNotifyTwoText 方法调用 Control.Invoke

要使Control.Invoke 工作,主线程必须是空闲的,但在您的情况下,您似乎通过在其中调用Parallel.ForEach 来阻塞主线程。

这是一个最小的复制:

private void button1_Click(object sender, EventArgs e)
{
    Parallel.ForEach(Enumerable.Range(1, 100), (i) =>
    {
        Thread.Sleep(10);//Simulate some work
        this.Invoke(new Action(() => SetText(i)));
    });
}

private void SetText(int i)
{
    textBox1.Text = i.ToString();
}

主线程等待Parallel.ForEach,工作线程等待主线程,从而导致死锁。

如何解决:不要使用 Invoke,只需使用 BeginInvoke 或不要阻塞 MainThread。

如果sscce 的帖子不是这种情况,那将对我们有所帮助

【讨论】:

  • @Charlie,使用asyncawait,如果你使用DownloadStringTaskAsync,它允许线程在你从Web客户端下载时让步给调度程序。
  • 我想问我将如何从我调用它的异步 DownloadString 事件处理程序更新特定线程的文档字符串 (html)。
【解决方案2】:

像这样更改您的代码,以使用async and await。这是使用BeginInvoke 和其他异步代码模型的现代替代方案。

private async Task ReadInputFile()
{
    var collection = new ConcurrentBag<PropertyRecord>();
    var lines = System.IO.File.ReadLines(InputFileName);
    int i = 0;
    int RecordsCount = lines.Count();
    Parallel.ForEach(lines, line =>
    {
        if (string.IsNullOrWhiteSpace(line))
        {
            return;                    
        }

        var tokens = line.Split(',');
        var postalCode = tokens[0];
        var country = tokens.Length > 1 ? tokens[1] : "england";

        SetLabelNotifyTwoText(
            string.Format(
                "Reading PostCode {0} out of {1}"
                i,
                lines.Length));

        var tempRecord = await GetAllAddesses(postalCode, country);
        if (tempRecord != null)
        {
            foreach (PropertyRecord r in tempRecord)
            {
                collection.Add(r);
            }
        }    
    });
}

private async Task<List<PropertyRecord>> GetAllAddesses(
        string postalCode,
        string country = "england")
{
    SetLabelNotifyText("");
    progressBar1.Value = 0;
    progressBar1.Update();

    var records = new List<PropertyRecord>();
    using (WebClient w = new WebClient())
    {
        var url = CreateUrl(postalCode, country);
        var document = await w.DownloadStringTaskAsync(url);
        var pagesCount = GetPagesCount(document);
        if (pagesCount == null)
        {
            return null;
        }

        for (int i = 0; i < pagesCount; i++)
        {
            SetLabelNotifyText(
                string.Format(
                    "Reading Page {0} out of {1}",
                    i,
                    pagesCount - 1));

            url = CreateUrl(postalcode,country, i);
            document = await w.DownloadStringTaskAsync(url);
            var collection = Regex.Matches(
                document,
                "<div class=\"soldDetails\">(.|\\n|\\r)*?class=" +
                "\"soldAddress\".*?>(?<address>.*?)(</a>|</div>)" +
                "(.|\\n|\\r)*?class=\\\"noBed\\\">(?<noBed>.*?)" +
                "</td>|</tbody>");

            foreach (var match in collection)
            {
                var r = new PropertyRecord();

                var bedroomCount = match.Groups["noBed"].Value;
                if(!string.IsNullOrEmpty(bedroomCount))
                {
                    r.BedroomCount = bedroomCount;             
                }
                else
                {
                    r.BedroomCount = "-1";
                }

                r.address = match.Groups["address"].Value;

                var line = string.Format(
                    "\"{0}\",{1}",
                    r.address
                    r.BedroomCount);
                OutputLines.Add(line);

                Records.Add(r);
            }
        }
    }

    return Records;
}

那就这样称呼吧

ReadInputFile.Wait();

或者,更好的是,调用者是async

await ReadInputFile();

【讨论】:

  • +1 你的然后这样称呼它应该是await ReadInputFile()。不要混合异步和同步代码。一路异步(如果可能的话)。
  • @SriramSakthivel,这样会更好,但是调用方法必须是async
  • 是的,很可能 OP 会在按钮单击或某些用户界面的某处有此代码。这意味着事件处理程序可以标记为async,因此您可以在其中await。我知道不可能总是这样,这就是我写 (如果可能的话) 的原因
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-06-16
  • 2016-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多