Inside my application I have list of persons from my database.
For every person I must call 5 (for now) services to search for some informations.
If service returns info I' adding it to that person (list of orders for specific person)
Because services work independent I thought I could try to run them parallel.
I've created my code as so:
using System;
using System.Collections.Generic;
using System.Threading;
namespace Testy
{
internal class Program
{
internal class Person
{
public int Id { get; set; }
public string Name { get; set; }
public List<string> Orders { get; private set; }
public Person()
{
// thanks for tip @juharr
Orders = new List<string>();
}
public void AddOrder(string order)
{
lock (Orders) //access across threads
{
Orders.Add(order);
}
}
}
internal class Service
{
public int Id { get; private set; }
public Service(int id)
{
Id = id;
}
//I get error when I use IList instead of List
public void Search(ref List<Person> list)
{
foreach (Person p in list)
{
lock (p) //should I lock Person here? and like this???
{
Search(p);
}
}
}
private void Search(Person p)
{
Thread.Sleep(50);
p.AddOrder(string.Format("test order from {0,2}",
Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(100);
}
}
private static void Main()
{
//here I load my services from external dll's
var services = new List<Service>();
for (int i = 1; i <= 5; i++)
{
services.Add(new Service(i));
}
//sample data load from db
var persons = new List<Person>();
for (int i = 1; i <= 10; i++)
{
persons.Add(
new Person {Id = i,
Name = string.Format("Test {0}", i)});
}
Console.WriteLine("Number of services: {0}", services.Count);
Console.WriteLine("Number of persons: {0}", persons.Count);
ManualResetEvent resetEvent = new ManualResetEvent(false);
int toProcess = services.Count;
foreach (Service service in services)
{
new Thread(() =>
{
service.Search(ref persons);
if (Interlocked.Decrement(ref toProcess) == 0)
resetEvent.Set();
}
).Start();
}
// Wait for workers.
resetEvent.WaitOne();
foreach (Person p in persons)
{
Console.WriteLine("{0,2} Person name: {1}",p.Id,p.Name);
if (null != p.Orders)
{
Console.WriteLine(" Orders:");
foreach (string order in p.Orders)
{
Console.WriteLine(" Order: {0}", order);
}
}
else
{
Console.WriteLine(" No orders!");
}
}
Console.ReadLine();
}
}
}
I have 2 problems with my code:
When I run my app I should get list of 10 persons and for every person 5 orders, but from time to time (ones for 3-5 runs) for first person I get only 4 orders. How I can prevent such behaviour?
solved! thanks to @juharr- How can I report progress from my threads? What I would like to get is one Function from my Program class that will be called every time order is added from service - I need that to show some kind of progress for every report.
I was trying solution described here: https://stackoverflow.com/a/3874184/965722, but I'm wondering if there is an easier way. Ideally I would like to add delegate toService
class and place all Thread code there.
How should I add event and delegate toService
class and how to subscribe to it in Main method?
I'm using .NET 3.5
I've added this code to be able to get progress reports:
using System;
using System.Collections.Generic;
using System.Threading;
namespace Testy
{
internal class Program
{
public class ServiceEventArgs : EventArgs
{
public ServiceEventArgs(int sId, int progress)
{
SId = sId;
Progress = progress;
}
public int SId { get; private set; }
public int Progress { get; private set; }
}
internal class Person
{
private static readonly object ordersLock = new object();
public int Id { get; set; }
public string Name { get; set; }
public List<string> Orders { get; private set; }
public Person()
{
Orders = new List<string>();
}
public void AddOrder(string order)
{
lock (ordersLock) //access across threads
{
Orders.Add(order);
}
}
}
internal class Service
{
public event EventHandler<ServiceEventArgs> ReportProgress;
public int Id { get; private set; }
public string Name { get; private set; }
private int counter;
public Service(int id, string name)
{
Id = id;
Name = name;
}
public void Search(List<Person> list) //I get error when I use IList instead of List
{
counter = 0;
foreach (Person p in list)
{
counter++;
Search(p);
Thread.Sleep(3000);
}
}
private void Search(Person p)
{
p.AddOrder(string.Format("Order from {0,2}", Thread.CurrentThread.ManagedThreadId));
EventHandler<ServiceEventArgs> handler = ReportProgress;
if (handler != null)
{
var e = new ServiceEventArgs(Id, counter);
handler(this, e);
}
}
}
private static void Main()
{
const int count = 5;
var services = new List<Service>();
for (int i = 1; i <= count; i++)
{
services.Add(new Service(i, "Service " + i));
}
var persons = new List<Person>();
for (int i = 1; i <= 10; i++)
{
persons.Add(new Person {Id = i, Name = string.Format("Test {0}", i)});
}
Console.WriteLine("Number of services: {0}", services.Count);
Console.WriteLine("Number of persons: {0}", persons.Count);
Console.WriteLine("Press ENTER to start...");
Console.ReadLine();
ManualResetEvent resetEvent = new ManualResetEvent(false);
int toProcess = services.Count;
foreach (Service service in services)
{
new Thread(() =>
{
service.ReportProgress += service_ReportProgress;
service.Search(persons);
if (Interlocked.Decrement(ref toProcess) == 0)
resetEvent.Set();
}
).Start();
}
// Wait for workers.
resetEvent.WaitOne();
foreach (Person p in persons)
{
if (p.Orders.Count != count)
Console.WriteLine("{0,2} Person name: {1}, orders: {2}", p.Id, p.Name, p.Orders.Count);
}
Console.WriteLine("END :)");
Console.ReadLine();
}
private static void service_ReportProgress(object sender, ServiceEventArgs e)
{
Console.CursorLeft = 0;
Console.CursorTop = e.SId;
Console.WriteLine("Id: {0,2}, Name: {1,2} - Progress: {2,2}", e.SId, ((Service) sender).Name, e.Progress);
}
}
}
I've added custom EventArgs, event for Service class.
In this configuration I should have 5 services running, but only 3 of them report progress.
I imagined that if I have 5 services I should have 5 events (5 lines showing progress).
This is probably because of threads, but I have no ideas how to solve this.
Sample output now looks like this:
Number of services: 5
Number of persons: 10
Press ENTER to start...
Id: 3, Name: Service 3 - Progress: 10
Id: 4, Name: Service 4 - Progress: 10
Id: 5, Name: Service 5 - Progress: 19
END :)
It should look like this:
Number of services: 5
Number of persons: 10
Press ENTER to start...
Id: 1, Name: Service 1 - Progress: 10
Id: 2, Name: Service 2 - Progress: 10
Id: 3, Name: Service 3 - Progress: 10
Id: 4, Name: Service 4 - Progress: 10
Id: 5, Name: Service 5 - Progress: 10
END :)
Last edit
I've moved all my thread creation to separate class ServiceManager
now my code looks like so:
using System;
using System.Collections.Generic;
using System.Threading;
namespace Testy
{
internal class Program
{
public class ServiceEventArgs : EventArgs
{
public ServiceEventArgs(int sId, int progress)
{
SId = sId;
Progress = progress;
}
public int SId { get; private set; } // service id
public int Progress { get; private set; }
}
internal class Person
{
private static readonly object ordersLock = new object();
public int Id { get; set; }
public string Name { get; set; }
public List<string> Orders { get; private set; }
public Person()
{
Orders = new List<string>();
}
public void AddOrder(string order)
{
lock (ordersLock) //access across threads
{
Orders.Add(order);
}
}
}
internal class Service
{
public event EventHandler<ServiceEventArgs> ReportProgress;
public int Id { get; private set; }
public string Name { get; private set; }
public Service(int id, string name)
{
Id = id;
Name = name;
}
public void Search(List<Person> list)
{
int counter = 0;
foreach (Person p in list)
{
counter++;
Search(p);
var e = new ServiceEventArgs(Id, counter);
OnReportProgress(e);
}
}
private void Search(Person p)
{
p.AddOrder(string.Format("Order from {0,2}", Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(50*Id);
}
protected virtual void OnReportProgress(ServiceEventArgs e)
{
var handler = ReportProgress;
if (handler != null)
{
handler(this, e);
}
}
}
internal static class ServiceManager
{
private static IList<Service> _services;
public static IList<Service> Services
{
get
{
if (null == _services)
Reload();
return _services;
}
}
public static void RunAll(List<Person> persons)
{
ManualResetEvent resetEvent = new ManualResetEvent(false);
int toProcess = _services.Count;
foreach (Service service in _services)
{
var local = service;
local.ReportProgress += ServiceReportProgress;
new Thread(() =>
{
local.Search(persons);
if (Interlocked.Decrement(ref toProcess) == 0)
resetEvent.Set();
}
).Start();
}
// Wait for workers.
resetEvent.WaitOne();
}
private static readonly object consoleLock = new object();
private static void ServiceReportProgress(object sender, ServiceEventArgs e)
{
lock (consoleLock)
{
Console.CursorTop = 1 + (e.SId - 1)*2;
int progress = (100*e.Progress)/100;
RenderConsoleProgress(progress, '■', ConsoleColor.Cyan, String.Format("{0} - {1,3}%", ((Service) sender).Name, progress));
}
}
private static void ConsoleMessage(string message)
{
Console.CursorLeft = 0;
int maxCharacterWidth = Console.WindowWidth - 1;
if (message.Length > maxCharacterWidth)
{
message = message.Substring(0, maxCharacterWidth - 3) + "...";
}
message = message + new string(' ', maxCharacterWidth - message.Length);
Console.Write(message);
}
private static void RenderConsoleProgress(int percentage, char progressBarCharacter,
ConsoleColor color, string message)
{
ConsoleColor originalColor = Console.ForegroundColor;
Console.ForegroundColor = color;
Console.CursorLeft = 0;
int width = Console.WindowWidth - 1;
var newWidth = (int) ((width*percentage)/100d);
string progBar = new string(progressBarCharacter, newWidth) + new string(' ', width - newWidth);
Console.Write(progBar);
if (!String.IsNullOrEmpty(message))
{
Console.CursorTop++;
ConsoleMessage(message);
Console.CursorTop--;
}
Console.ForegroundColor = originalColor;
}
private static void Reload()
{
if (null == _services)
_services = new List<Service>();
else
_services.Clear();
for (int i = 1; i <= 5; i++)
{
_services.Add(new Service(i, "Service " + i));
}
}
}
private static void Main()
{
var services = ServiceManager.Services;
int count = services.Count;
var persons = new List<Person>();
for (int i = 1; i <= 100; i++)
{
persons.Add(new Person {Id = i, Name = string.Format("Test {0}", i)});
}
Console.WriteLine("Services: {0}, Persons: {1}", services.Count, persons.Count);
Console.WriteLine("Press ENTER to start...");
Console.ReadLine();
Console.Clear();
Console.CursorVisible = false;
ServiceManager.RunAll(persons);
foreach (Person p in persons)
{
if (p.Orders.Count != count)
Console.WriteLine("{0,2} Person name: {1}, orders: {2}", p.Id, p.Name, p.Orders.Count);
}
Console.CursorTop = 12;
Console.CursorLeft = 0;
Console.WriteLine("END :)");
Console.CursorVisible = true;
Console.ReadLine();
}
}
}