C#中的基本多线程问题

时间:2011-10-31 15:09:22

标签: c# winforms multithreading

我试图使用几个线程来完成以下任务:

 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;

虽然count是在类级别定义的,progressbar是progressbar(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 \ progressbar。

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

tyvm求助:)

4 个答案:

答案 0 :(得分:1)

使用Windows格式,在MSDN中使用进度条和线程的最佳方法是BackgroundWorkers就是一个很好的例子。

答案 1 :(得分:1)

您无法从不是表单线程的线程访问Windows窗体或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而不是lock,它更快。 在谷歌上看看Interlocked.Increment,这是一个非常好的功能:原子增量。

答案 2 :(得分:1)

我同意其他建议使用.NET线程池而不是自己启动新线程的答案。如果您有使用.NET 4或更高版本的奢侈品,我会更进一步,建议您使用Task Parallel Library(即使您当前没有使用.NET 4,也可以构建面向任务的抽象。 .NET线程池非常简单。)

使用任务IMHO的最大优势之一是它们是即将推出的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毫秒之间),并发运行的任务数量取决于您可用的处理器/核心数量(任务并行库自动执行此操作),因此时间显示的运行会有所不同。

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

  > 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 

答案 3 :(得分:0)

正如一些人已经提到的,然后使用ThreadPool而不是自己创建线程。通过使用ThreadPool,您不必担心要生成的线程数 - ThreadPool将为您执行此操作。

假设您使用的是.NET 4,您可以利用TPL:

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));
    }
}