2011-10-31 61 views
2

我尝试用几个线程做出如下:基本的多线程问题

List<Thread> threads = new List<Thread>(); 
     for (int i = 0; i < 5; i++) 
      threads.Add(new Thread(CheckTradeOneForOne)); 

     foreach (Thread t in threads) 
      t.Start(); 
      progressBarTrades.Value = 0; 
     count = 0; 

同时计数在类级别定义,进度就是进度(WinForms的^ _ ^)。

private void CheckTradeOneForOne() 
    { 
     int current; 
     while (count < Trades.Count) 
     { 
      lock (locker) 
      { 
       current = count; 
       count++; 
      } 
      temppage = HelpClass.GetSourceCodeForTrade(Trades[current], sessid, profilenum); 
      //if the trainer has requested anything? 
      if (!HelpClass.RequestAnything(temppage)) 
      { 

      } 
      lock (locker) 
      { 
       progressBarTrades.Value = (100 * count)/Trades.Count; 
       //buttonAction.Text = count.ToString(); 
      } 
     } 
    } 

交易是列表(长度约1000)。

我想通过所有的交易,使一个HttpWebRequest的每一个, 因此我想用多线程, 的问题是,虽然我使用锁即时得到错误关于第二锁定另一个线程使用buttonaction \进度。

还有一件事,这是使用多线程的正确方法吗? 多少线程是理想的使用? 并做锁确保没有moe,然后一个线程采取相同的交易字符串?

tyvm您的帮助:)

+1

使用线程池(OF-话题,但强烈建议) – Burimi

回答

1

与Windows形成与进度条和线程在MSDN BackgroundWorkers工作的最好办法就是一个很好的例子。

1

您不能从非窗体线程的线程访问窗体窗体或wpf控件。 要做到这一点,你应该使用BeginInvoke或类SynchronizationContext。

但是,由于您只是更新进度条,我建议您使用简单的计时器,如果您有很多线程,它会更快。

取而代之的线程我也建议你使用ThreadPool,这将避免太多线程的实例化。

就像我举一个例子,我张贴了一些代码。

private volatile int TradesCount; 
private List<...> Trades; 

void MyFunction() 
{ 
    List<Thread> threads = new List<Thread>(); 
    for (int i = 0; i < 5; i++) 
     threads.Add(new Thread(CheckTradeOneForOne)); 

    timer1.Enabled = true; 

    progressBarTrades.Value = 0; 
    this.TradesCount = 0; 

    foreach (Thread t in threads) 
     t.Start(); 
} 

void timer1_Tick(object sender, EventArgs e) 
{ 
    int count = this.TradesCount; 
    if (count >= Trades.Count) 
    { 
     count = Trades.Count; 
     timer1.Enabled = false; 
    } 

    progressBarTrades.Value = (100 * count)/Trades.Count; 
    buttonAction.Text = count.ToString(); 
} 

private void CheckTradeOneForOne() 
{ 
    int current; 
    for (;;) 
    { 
     // This will give you a warning, but you can ignore it with a #pragma, it is allowed to use Interlocked.Increment and volatile fields. 
     current = Interlocked.Increment(ref TradesCount) - 1; 

     if (current >= Trades.Count) 
      break; // We can exit the loop. 

     temppage = HelpClass.GetSourceCodeForTrade(Trades[current], sessid, profilenum); 

     //if the trainer has requested anything? 
     if (!HelpClass.RequestAnything(temppage)) 
     { 
      ... 
     } 
    } 
} 

我使用Interlocked.Increment而不是锁定,它更快。 看看谷歌有关Interlocked.Increment,这是一个很好的功能:原子增量。

+0

tyvm非常详细的评论,谷歌上搜索interlocked.increment我后看到它锁得更快。 但我只剩下2个问题: 1)SynchronizationContext.Current,souhld我每次使用这个使用多线程? 2)怎么知道多少线程会给我最好的性能? tyvm :) – aliyaho

+0

SynchronizationContext是一个允许您将函数作为“消息”发布到其他线程(例如,窗口窗体线程)的类。这将允许您从另一个线程更新GUI。 –

+0

关于计算线程数......它取决于...我不知道你的情况。 –

1

我同意其他答案建议使用.NET线程池,而不是自己开始新线程。如果您有使用.NET 4或更高版本的优点,我会走得更远,并建议您使用Task Parallel Library(即使您目前不使用.NET 4,也可以在.NET线程的基础上构建面向任务的抽象,游泳池非常简单)。

使用任务恕我直言,最大的优点之一是,他们是即将到来的C#5 await内置语言功能的基础。因此,如果您今天使用任务进行后台处理,那么您已经准备好了将来的计划:-)。

要告诉你如何使用任务解决您的眼前的问题,我写了一个简单的WinForms程序,说明做后台处理和更新进度条的技术来跟踪有多少任务已经完成:

// file "Program.cs" 
using System; 
using System.Diagnostics; 
using System.Linq; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace ProgressDialog 
{ 
    static class Program 
    { 
     [STAThread] static void Main() 
     { 
      // build UI 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 
      var rootForm = new Form { Text = @"Test Form", Width = 300, Height = 100 }; 
      var btn = new Button { Text = @"Start", Parent = rootForm, Dock = DockStyle.Top }; 
      var progress = new ProgressBar { Minimum = 0, Parent = rootForm, Dock = DockStyle.Top, Style = ProgressBarStyle.Continuous }; 
      new Label { Text = @"Progress:", Parent = rootForm, Dock = DockStyle.Top, AutoSize = true }; 


      // define parameters 
      const int sourcesCount = 20; // how many sources do we want to process 
      var completedCount = 0; 
      var randomGenerator = new Random(); 
      var timer = new Stopwatch(); 


      // callback that will be invoked on UI thread each time a task finishes 
      Action<int> onTaskCompleted = source => 
      { 
       ++completedCount; // we're modifying "completedCount" closure on purpose 
       progress.Value = completedCount; 
       System.Diagnostics.Debugger.Log(0, null, string.Concat(@"UI notified that task for source ", source, @" finished; overall ", completedCount, @" tasks finished", Environment.NewLine)); 
       if (completedCount == sourcesCount) 
       { 
        timer.Stop(); 
        btn.Enabled = true; 
        btn.Text = string.Concat(@"Finished (took ", timer.ElapsedMilliseconds, @" milliseconds). Start again"); 
       } 
      }; 


      // task itself (the hard part :)) 
      Action<int> task = source => 
      { 
       System.Diagnostics.Debugger.Log(0, null, string.Concat(@" > Starting task for source ", source, Environment.NewLine)); 
       Thread.Sleep(randomGenerator.Next(100, 200)); // simulate some workload (taking between 100 and 200 milliseconds) 
       System.Diagnostics.Debugger.Log(0, null, string.Concat(@" < Finished task for source ", source, Environment.NewLine)); 
       rootForm.BeginInvoke(new Action(() => onTaskCompleted(source))); 
      }; 


      // start button handler (kick-starts the background tasks) 
      btn.Click += (src, args) => 
      { 
       btn.Enabled = false; 
       btn.Text = @"Running..."; 
       progress.Maximum = sourcesCount; 
       progress.Value = 0; 
       timer.Restart(); 

       completedCount = 0; 
       var sources = Enumerable.Range(1, sourcesCount); // simulate getting data for each task 
       var tasks = sources 
        .Select(s => Task.Factory.StartNew(() => task(s))) // at this point we only have an enumerable that is able to start all the tasks, nothing is running yet 
        .ToArray(); // now the tasks are started 

       if (tasks.Length != sourcesCount) { throw new InvalidOperationException(); } // assert that we created one task for each source 
      }; 


      // show the form now, let the user interact with it 
      Application.Run(rootForm); 
     } 
    } 
} 

您可以通过在Visual Studio中创建新(控制台或winforms)项目并将代码复制到Program.cs或在命令行上使用csc.exe来编译该程序。

当任务运行时,进度条轨道已完成的任务数:

Running tasks

当所有任务都完成后,启动按钮显示拍摄总时间:

Finished tasks

注每个任务所花费的时间是随机的(100到200毫秒之间),同时运行的任务数将取决于您有多少处理器/内核可用(任务P arallel Library会自动执行此操作),所以显示的时间将在不同运行之间变化。

另请注意,诊断消息在调试模式下运行程序时(或者可以使用SysInternals DebugView查看它们)发送到Visual Studio中的“输出”视图。我(2芯)机上一个样品运行产生以下:

> Starting task for source 1 
    > Starting task for source 2 
    > Starting task for source 3 
    < Finished task for source 3 
    > Starting task for source 4 
UI notified that task for source 3 finished; overall 1 tasks finished 
    < Finished task for source 2 
    > Starting task for source 5 
UI notified that task for source 2 finished; overall 2 tasks finished 
    < Finished task for source 1 
    > Starting task for source 6 
UI notified that task for source 1 finished; overall 3 tasks finished 
    < Finished task for source 4 
    > Starting task for source 7 
UI notified that task for source 4 finished; overall 4 tasks finished 
    < Finished task for source 5 
    > Starting task for source 8 
UI notified that task for source 5 finished; overall 5 tasks finished 
    < Finished task for source 6 
    > Starting task for source 9 
UI notified that task for source 6 finished; overall 6 tasks finished 
    < Finished task for source 8 
    > Starting task for source 10 
UI notified that task for source 8 finished; overall 7 tasks finished 
    < Finished task for source 7 
    > Starting task for source 11 
UI notified that task for source 7 finished; overall 8 tasks finished 
    < Finished task for source 9 
    > Starting task for source 12 
UI notified that task for source 9 finished; overall 9 tasks finished 
    < Finished task for source 10 
    < Finished task for source 11 
    > Starting task for source 13 
UI notified that task for source 10 finished; overall 10 tasks finished 
UI notified that task for source 11 finished; overall 11 tasks finished 
    > Starting task for source 14 
    < Finished task for source 14 
    > Starting task for source 15 
UI notified that task for source 14 finished; overall 12 tasks finished 
    < Finished task for source 13 
    > Starting task for source 16 
UI notified that task for source 13 finished; overall 13 tasks finished 
    < Finished task for source 12 
    > Starting task for source 17 
UI notified that task for source 12 finished; overall 14 tasks finished 
    < Finished task for source 16 
    > Starting task for source 18 
UI notified that task for source 16 finished; overall 15 tasks finished 
    < Finished task for source 15 
UI notified that task for source 15 finished; overall 16 tasks finished 
    > Starting task for source 19 
    < Finished task for source 17 
UI notified that task for source 17 finished; overall 17 tasks finished 
    < Finished task for source 18 
    > Starting task for source 20 
UI notified that task for source 18 finished; overall 18 tasks finished 
    < Finished task for source 19 
UI notified that task for source 19 finished; overall 19 tasks finished 
    < Finished task for source 20 
UI notified that task for source 20 finished; overall 20 tasks finished 
0

正如已经提到的一些,然后用ThreadPool,而不是创建自己的线程。通过使用ThreadPool,您不必担心产生的线程数 - ThreadPool将为您做到这一点。

假设你正在使用.NET 4,你可以利用第三方物流:

public partial class Form1 : Form 
{ 
    private volatile int count; 
    private readonly int total; 

    public Form1() 
    { 
     InitializeComponent(); 

     var urls = new List<string> { "http://something.com", "http://another.com" }; 
     total = urls.Count; 

     // Execute the Parallel loop in a thread from the threadpool, 
     // in order not to block the UI thread. 
     ThreadPool.QueueUserWorkItem(o => 
     { 
      Parallel.ForEach(urls, x => MakeRequest(x)); 
     }); 

     // other UI stuff here? 
    } 

    public void MakeRequest(string url) 
    { 
     // code for web request here... 

     int newCount = Interlocked.Increment(ref count); 
     Invoke(new Action(() => progressBar.Value = (100 * newCount)/total)); 
    } 
}