在 .NET 4.0 中将任务与 Parallel.Foreach 一起使用

Using Task with Parallel.Foreach in .NET 4.0

我开始尝试向 windows 表单添加一个进度条,用于更新 Parallel.Foreach 循环中代码 运行ning 的进度。为此,UI 线程必须可用于更新进度条。我使用任务 运行 Parallel.Foreach 循环来允许 UI 线程更新进度条。

在 Parallel.Foreach 循环中完成的工作相当密集。在 运行 将程序的可执行文件(未在 visual studio 内调试)与任务连接后,程序变得无响应。如果我 运行 我的程序没有任务,情况就不是这样了。我注意到这两个实例之间的主要区别是,当 运行 没有任务时,程序占用了 ~80% 的 cpu,而当 运行 有任务时,程序占用了 ~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.) 为什么在负载小得多的情况下,带有任务的程序会变得无响应?

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

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

我很感激任何帮助或想法,我花了很多时间研究这个。如果我违反了任何发布格式或规则,我也深表歉意。我试着遵守它们,但可能遗漏了一些东西。

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

来自MSDN

Executes the specified delegate on the thread that owns the control's underlying window handle.

The Invoke method searches up the control's parent chain until it finds a control or form that has a window handle if the current control's underlying window handle does not exist yet.

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.) Why would the program with the Task become unresponsive when the load is much less?

我不是 100% 确定,但这可能与您在 UI 线程上锁定成员有关。如果负载较小,那么锁定将更频繁地发生,可能会导致 UI 线程在进度条递增时 "hang"。

您也是 运行 一个 while 循环,它每 100 毫秒使 UI 线程休眠一次。由于 while 循环,您会看到 UI 挂起。

2.) Does using Task to run Parallel.Foreach limit the thread pool of the Parallel.Foreach to only the thread running the task?

没有。 Parallel.ForEach 调用中将创建多个任务。底层 ForEach 使用 partitioner 来分散工作,并且不会创建比必要更多的任务。它批量创建任务,并处理批次。

3.) Is there a way to make the UI thread responsive instead of sleeping for the .1 second duration without using cancellation token?

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