2015-05-29 37 views
4

我开始尝试向Windows窗体添加进度条,该窗体更新在Parallel.Foreach循环内运行的代码的进度。为了做到这一点,UI线程必须可用来更新进度条。我使用了一个Task来运行Parallel.Foreach循环以允许UI线程更新进度条。在.NET 4.0中使用Parallel.Foreach任务

在Parallel.Foreach循环内完成的工作相当密集。使用Task运行程序的可执行文件(不在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.)使用Task来运行Parallel.Foreach是否将Parallel.Foreach的线程池限制为仅运行任务的线程?

3.)有没有办法让UI线程响应,而不是在没有使用取消标记的情况下睡1秒的时间?

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

+0

等待在UI线程上循环完成的任务就像在UI线程上执行它(稍微差一点)。看看'BackgroundWorker'或'async' – SimpleVar

+0

如果'DoWork()'访问你的任何UI元素,它将导致死锁发生。 –

+0

JohnathonSullinger - DoWork()不访问任何UI元素。 @Yorye Nathan - 你能否详细说明或举个例子?今天之前我从来没有用过多线程做过什么。 –

回答

6

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

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)为什么会与工作程序无响应时的负荷要少得多?

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

您还正在运行一个while循环,该循环每100毫秒就会在UI线程中休眠。由于while循环,你会看到UI挂起。

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

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

3.)有没有办法让UI线程响应,而不是在不使用取消标记的情况下在.1秒内休眠?

我能够通过移除while回路,并使用Invoke方法先走一步,直接在UI线程上执行拉姆达来处理。

+0

这是.Net 4.5的一个很好的解决方案,但问题被标记为.net-4.0。 OP可以使用['BeginInvoke()'](https://msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke%28v=vs.110%29.aspx)来更新进度条? – dbc

+0

啊,我错过了。是的他们可以。我会用一个例子更新我的答案。 –

+0

感谢您的回应!不幸的是,我正在使用的项目是使用.NET 4.0,据我所知,它不支持Progress(T)类。 编辑:没有看到有人已经发现了。 –