多线程问题,可能是使用Foreach的死锁

本文关键字:Foreach 死锁 问题 多线程 | 更新日期: 2023-09-27 18:30:03

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-方法返回后,Step Next按钮停止,它只是在后台继续调试。我放的任何书签上都找不到它。

多线程问题,可能是使用Foreach的死锁

正如您在评论中所说,您的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后不是这样,这将对我们有帮助

这样更改代码,使用asyncawait。这是使用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();