основные вопросы многопоточности в C # - PullRequest
2 голосов
/ 31 октября 2011

я пытаюсь использовать несколько потоков, чтобы сделать следующее:

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

Trades is List (длина около 1000).

Я хочу пройти все сделки и сделать по одному httpwebrequest для каждой, поэтому я хочу использовать многопоточность, проблема в том, что, хотя я использую блокировку, я получаю ошибкувторая блокировка, которую другой поток использует buttonaction \ progressbar.

еще одна вещь, это правильный способ использовать многопоточность?сколько потоков идеально подходит для использования?и убедитесь, что блокировка гарантирует, что ни один поток не возьмет такую ​​же торговую строку?

tyvm за вашу помощь:)

Ответы [ 4 ]

1 голос
/ 31 октября 2011

Вы не можете получить доступ к окнам формы или элементам управления wpf из потока, который не является потоком формы.Для этого вам следует использовать BeginInvoke или класс SynchronizationContext.

Однако, поскольку вы просто обновляете индикатор выполнения, я бы предложил вам использовать простой таймер, если у вас много потоков, он будет быстрее.1003 *

Вместо потоков Я бы также предложил вам использовать ThreadPool, это позволит избежать создания слишком большого количества потоков.

Просто в качестве примера я публикую некоторый код.Я использую Interlocked.Increment вместо блокировки, это быстрее.Посмотрите на Google о Interlocked.Increment, это очень хорошая функция: атомный прирост.

1 голос
/ 31 октября 2011

Я согласен с другими ответами, рекомендующими использовать пул потоков .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);
        }
    }
}

Вы можете скомпилировать программу, создав новый проект (console или winforms) в Visual Studio и скопировав код в Program.cs или используя csc.exe в командной строке.

Когда задачи выполняются, индикатор выполнения отслеживает количество выполненных задач:

Running tasks

Когда все задачи завершены, кнопка «Пуск» отображает общее время:

Finished tasks

Обратите внимание, что время, затрачиваемое каждой задачей, является случайным (от 100 до 200 миллисекунд), и количество одновременно выполняемых задач будет зависеть от того, сколько процессоров / ядер у вас имеется (Task Parallel Library делает это автоматически), поэтому отображается будет варьироваться между пробегами.

Также обратите внимание, что диагностические сообщения отправляются в представление «Вывод» в Visual Studio при запуске программы в режиме отладки (или вы можете использовать SysInternals DebugView для их просмотра). Один пробный прогон на моем (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 
1 голос
/ 31 октября 2011

Для Windows лучше всего работать с индикатором выполнения, а потоков - BackgroundWorkers в MSDN - хороший пример.

0 голосов
/ 31 октября 2011

Как уже упоминалось некоторыми, затем используйте 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));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...