У меня есть приложение для телефонных сообщений, в котором нужно обработать очень много сообщений, поскольку телефонные порты ограничены, поэтому сообщение будет обработано в первую очередь. У каждого сообщения есть флаг «Подтверждение», который указывает, какое сообщение обрабатывается. Конечно, он был инициализирован как ложный.

Я хочу поместить все сообщения в очередь, а затем обработать их несколькими потоками или задачами.

    public class MessageQueue
    {
        public Queue MessageWorkItem { get; set; }
        public Messages Message { get; set; }
        public MessageQueue()
        {
            MessageWorkItem = new Queue();
            Message = new Messages();
        }
        public void GetMessageMetaData()
        {
            try
            {
                // It is just a test, add only one item into the queue
                Message.MessageID = Guid.NewGuid();
                Message.NumberToCall = "1111111111";
                Message.FacilityID = "3333";
                Message.NumberToDial = "2222222222";
                Message.CountryCode = "1";
                Message.Acknowledge = false;
            }
            catch (Exception ex)
            {
            }
        }

        public void AddingItemToQueue()
        {
            GetMessageMetaData();
            if (!Message.Acknowledge)
            {
                lock (MessageWorkItem)
                {
                    MessageWorkItem.Enqueue(Message);
                }
            }
        }
    }

    public class Messages
    {
        public Guid MessageID { get; set; }
        public string NumberToCall { get; set; }
        public string FacilityID { get; set; }
        public string NumberToDial { get; set; }
        public string CountryCode { get; set; }
        public bool Acknowledge { get; set; }
    }

Теперь у меня вопрос, как удалить элемент из очереди с помощью многопоточности. Для каждого элемента из очереди я хочу запустить сценарий.

        public void RunScript(Message item)
        {
            try
            {
                PlayMessage(item); 
                return;
            }
            catch (HangupException hex)
            {
                Log.WriteWithId("Caller Hungup!", hex.Message);
            }
            catch (Exception ex)
            {
                Log.WriteException(ex, "Unexpected exception: {0}");
            }
        }

Я думал, что посмотреть, если

if (MessageWorkItem.Count> = 1) Затем что-то делаю, но мне нужна помощь по коду.

14
user1108948 27 Мар 2014 в 17:09
2
Посмотрите на образец (проблему) производитель / потребитель и полностью реорганизуйте свои классы. Обратите внимание на именование.
 – 
Hamlet Hakobyan
27 Мар 2014 в 17:16
Какую версию фреймворка вы используете
 – 
BRAHIM Kamel
27 Мар 2014 в 18:16
@@ K.B, .NET 4 или .NET 4.5. Какое бы то ни было нормально.
 – 
user1108948
27 Мар 2014 в 18:21

2 ответа

Лучший ответ

Если вы можете использовать .Net 4.5, я бы посоветовал взглянуть на поток данных из задачи Параллельная библиотека (TPL).

Эта страница приводит к множеству примеров пошаговых руководств, таких как Как: реализовать шаблон потока данных производитель-потребитель и Пошаговое руководство: использование потока данных в приложении Windows Forms.

Взгляните на эту документацию, чтобы узнать, поможет ли она вам. Это довольно много, но я думаю, что это, вероятно, ваш лучший подход.

В качестве альтернативы вы можете изучить использование {{X0 }} вместе с его методом GetConsumingEnumerable() для доступа к элементам в очереди.

Что вы делаете, так это разбиваете работу на объекты, которые хотите каким-то образом обработать, и используете BlockingCollection для управления очередью.

Пример кода, использующего ints вместо объектов в качестве рабочих элементов, поможет продемонстрировать это:

Когда рабочий поток завершит работу со своим текущим элементом, он удалит новый элемент из рабочей очереди, обработает этот элемент, а затем добавит его в очередь вывода.

Отдельный потребительский поток удаляет завершенные элементы из очереди вывода и что-то с ними делает.

В конце мы должны дождаться завершения всех рабочих процессов (Task.WaitAll (worker)), прежде чем мы сможем отметить очередь вывода как завершенную (outputQueue.CompleteAdding ()).

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            new Program().run();
        }

        void run()
        {
            int threadCount = 4;
            Task[] workers = new Task[threadCount];

            Task.Factory.StartNew(consumer);

            for (int i = 0; i < threadCount; ++i)
            {
                int workerId = i;
                Task task = new Task(() => worker(workerId));
                workers[i] = task;
                task.Start();
            }

            for (int i = 0; i < 100; ++i)
            {
                Console.WriteLine("Queueing work item {0}", i);
                inputQueue.Add(i);
                Thread.Sleep(50);
            }

            Console.WriteLine("Stopping adding.");
            inputQueue.CompleteAdding();
            Task.WaitAll(workers);
            outputQueue.CompleteAdding();
            Console.WriteLine("Done.");

            Console.ReadLine();
        }

        void worker(int workerId)
        {
            Console.WriteLine("Worker {0} is starting.", workerId);

            foreach (var workItem in inputQueue.GetConsumingEnumerable())
            {
                Console.WriteLine("Worker {0} is processing item {1}", workerId, workItem);
                Thread.Sleep(100);          // Simulate work.
                outputQueue.Add(workItem);  // Output completed item.
            }

            Console.WriteLine("Worker {0} is stopping.", workerId);
        }

        void consumer()
        {
            Console.WriteLine("Consumer is starting.");

            foreach (var workItem in outputQueue.GetConsumingEnumerable())
            {
                Console.WriteLine("Consumer is using item {0}", workItem);
                Thread.Sleep(25);
            }

            Console.WriteLine("Consumer is finished.");
        }

        BlockingCollection<int> inputQueue = new BlockingCollection<int>();
        BlockingCollection<int> outputQueue = new BlockingCollection<int>();
    }
}
19
Matthew Watson 27 Мар 2014 в 18:11
@@ Mattew, я могу использовать .net 4.5.
 – 
user1108948
27 Мар 2014 в 18:00
Ах, у вас был тег .Net 4.0 на ваш вопрос (кто-то уже исправил это для вас, я заметил! :)
 – 
Matthew Watson
27 Мар 2014 в 18:06
Я немного отредактировал свой ответ в свете вышеизложенной информации.
 – 
Matthew Watson
27 Мар 2014 в 18:11
- @ Мэттью, в моем конкретном случае, где мне разместить свой RunScript ()? Я думаю, что в void worker ()?
 – 
user1108948
27 Мар 2014 в 18:38
Да, это было бы подходящее место. Полезной нагрузкой будет workItem, который вы создадите любого типа, который вам нужен для хранения информации, которую вы хотите обрабатывать как единицу работы - возможно, экземпляр Message в вашем случае? В этом случае вы должны использовать BlockingCollection<Message>
 – 
Matthew Watson
27 Мар 2014 в 19:18

Parallel.ForEach из TPL. Это параллельно для каждого.

Пример (изменен MessageWorkItem на общую очередь):

    public class MessageQueue
{
    public Queue<Message> MessageWorkItem { get; set; }

    public MessageQueue()
    {
        MessageWorkItem = new Queue<Message>();
    }

    public Message GetMessageMetaData()
    {
        try
        {
            // It is just a test, add only one item into the queue
            return new Message()
            {
                MessageID = Guid.NewGuid(),
                NumberToCall = "1111111111",
                FacilityID = "3333",
                NumberToDial = "2222222222",
                CountryCode = "1",
                Acknowledge = false
            };
        }
        catch (Exception ex)
        {
            return null;
        }
    }

    public void AddingItemToQueue()
    {
        var message = GetMessageMetaData();
        if (!message.Acknowledge)
        {
            lock (MessageWorkItem)
            {
                MessageWorkItem.Enqueue(message);
            }
        }
    }
}

public class Message
{
    public Guid MessageID { get; set; }
    public string NumberToCall { get; set; }
    public string FacilityID { get; set; }
    public string NumberToDial { get; set; }
    public string CountryCode { get; set; }
    public bool Acknowledge { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        MessageQueue me = new MessageQueue();
        for (int i = 0; i < 10000; i++)
            me.AddingItemToQueue();

        Console.WriteLine(me.MessageWorkItem.Count);

        Parallel.ForEach(me.MessageWorkItem, RunScript);
    }

    static void RunScript(Message item)
    {
        // todo: ...
        Console.WriteLine(item.MessageID);
        Thread.Sleep(300);
    }
}
1
Exploding Kitten 27 Мар 2014 в 17:33
3
Это не сработает, если потребитель будет продолжать работать и добавлять что-то в очередь.
 – 
oleksii
27 Мар 2014 в 17:42
3
Вы также добавляете элементы в очередь во время итерации по ней, что вызовет исключение, а также будет подвергаться всевозможным условиям гонки из-за того, что это даже делается из разных потоков. Queue просто не создан для одновременного доступа из нескольких потоков.
 – 
Servy
27 Мар 2014 в 17:54
Вы на 100% правы в вопросе «добавление элементов в очередь во время итерации по ней», но одно уточнение я хочу сделать по вашему второму утверждению. Parallel.ForEach не будет выполнять многопоточный доступ к IEnumerable<T> с использованием модуля разделения по умолчанию, он будет извлекать кучу элементов во внутреннюю очередь, используя один поток, а затем передавать эту работу нескольким рабочим потокам. Но снова я подчеркиваю, что Servy прав, что чтение из этого единственного потока и запись с использованием одного или нескольких других потоков невозможно с перечислителем Queue.
 – 
Scott Chamberlain
27 Мар 2014 в 18:09