2011-10-31 34 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)); 
    } 
}