在.NET 4.0中使用Task with Parallel.Foreach

时间:2015-05-29 00:51:15

标签: c# multithreading .net-4.0 parallel-processing

我开始尝试向Windows窗体添加进度条,以更新在Parallel.Foreach循环中运行的代码的进度。为此,UI线程必须可用于更新进度条。我使用Task来运行Parallel.Foreach循环以允许UI线程更新进度条。

在Parallel.Foreach循环中完成的工作相当密集。在使用Task运行程序的可执行文件(不在visual studio中调试)之后,程序没有响应。如果我在没有Task的情况下运行程序,情况就不是这样。我注意到两个实例之间的关键区别在于程序在没有Task的情况下运行时占用了大约80%的cpu,在使用Task运行时大约占5%。

private void btnGenerate_Click(object sender, EventArgs e)
    {
        var list = GenerateList();
        int value = 0;
        var progressLock = new object ();

        progressBar1.Maximum = list.Count();

        Task t = new Task(() => Parallel.ForEach (list, item =>
        {
                DoWork ();
                lock (progressLock)
                {
                    value += 1;
                }
        }));

        t.Start();

        while (!t.IsCompleted)
        {
            progressBar1.Value = value;
            Thread.Sleep (100);
        }
    }

旁注:我知道

 Interlocked.Increment(ref int___);

代替锁定。它被认为更有效吗?

我的问题有三个方面:

1。)当负载少得多时,为什么带有Task的程序会无响应?

2.。)使用Task运行Parallel.Foreach是否将Parallel.Foreach的线程池限制为只运行任务的线程?

3。)有没有办法让UI线程响应而不是在没有使用取消令牌的情况下休眠0.1秒?

我很感激任何帮助或想法,我花了很多时间研究这个。如果我违反任何发布格式或规则,我也会道歉。我试图坚持他们,但可能错过了一些东西。

1 个答案:

答案 0 :(得分:6)

通过使用在拥有的Windows同步上下文中调用委托的内置Invoke方法,可以极大地简化代码。

来自MSDN

  

在拥有控件底层窗口句柄的线程上执行指定的委托。

     

Invoke方法搜索控件的父链,直到它找到一个具有窗口句柄的控件或窗体,如果当前控件的底层窗口句柄还不存在。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    string[] GenerateList() => new string[500];
    void DoWork()
    {
        Thread.Sleep(50);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var list = GenerateList();
        progressBar1.Maximum = list.Length;

        Task.Run(() => Parallel.ForEach(list, item =>
        {
            DoWork();

            // Update the progress bar on the Synchronization Context that owns this Form.
            this.Invoke(new Action(() => this.progressBar1.Value++));
        }));
    }
}

这将从任务中调用表单所属的同一UI线程上的Action委托。

现在尝试回答你的问题

  

1。)当负载少得多时,为什么带有Task的程序会无响应?

我不是百分百肯定,但这可能与您在UI线程上锁定成员有关。如果负载较小,则锁定将更频繁地发生,可能导致UI线程在进度条增加时“挂起”。

您还运行一个每100毫秒睡眠UI线程的while循环。你会看到UI因为while循环而挂起。

  

2.使用Task运行Parallel.Foreach是否将Parallel.Foreach的线程池限制为只运行任务的线程?

没有。将在Parallel.ForEach调用中创建多个任务。基础ForEach使用partitioner来扩展工作,而不是创建超出必要的任务。它分批创建任务,并处理批次。

  

3。)有没有办法让UI线程响应而不是在没有使用取消令牌的情况下休眠0.1秒?

我能够通过删除while循环并使用Invoke方法直接在UI线程上执行lambda来处理它。