Why is my console app not waiting? All the outputted times are the same.
I app hoping to execute the commands but not all at once I would like for the WaitAfterExecution to finish before executing the next command on so on. But also if I have some long-running tasking fetching data I would also like that to continue.
public class Qsend: IQCommand
{
public Qsend(Action p, int v1, int v2, string v3)
{
OnCommand = p;
WaitBeforeExecution = v1;
WaitAfterExecution = v2;
Name = v3;
}
public Action OnCommand { get ; set ; }
public int WaitBeforeExecution { get ; set ; }
public int WaitAfterExecution { get ; set ; }
public string Name { get ; set ; }
}
public interface IQCommand
{
Action OnCommand { get; set; }
int WaitBeforeExecution { get; set; }
int WaitAfterExecution { get; set; }
string Name { get; set; }
}
public static class CExtensions
{
public static void ProcessThis(this IQCommand irc)
{
Console.WriteLine("Process This Started!!!");
if (irc == null)
return;
if (irc.OnCommand == null)
return;
if (irc.WaitBeforeExecution > 0)
System.Threading.Thread.Sleep(irc.WaitBeforeExecution);
irc.OnCommand.Invoke();
if (irc.WaitAfterExecution > 0)
System.Threading.Thread.Sleep(irc.WaitAfterExecution);
Console.WriteLine("Process This End!!!");
}
}
Usage:
using System.Diagnostics;
List<IQCommand> commands = new List<IQCommand>();
commands.Add(new Qsend(() => { Console.WriteLine($"Command 1 {DateTime.Now.ToString("HH:mm:ss.ffffff")}"); }, 3, 6, "Name 1"));
commands.Add(new Qsend(() => { Console.WriteLine($"Command 2 { DateTime.Now.ToString("HH:mm:ss.ffffff")}"); }, 5, 10, "Name 2"));
// Creating object of ExThread class
Console.WriteLine($"Before EXE {DateTime.Now.ToString("HH:mm:ss.ffffff")}");
foreach (var item in commands)
{
Console.WriteLine(item.Name);
item.ProcessThis();
}
Console.WriteLine($"AFTER EXE {DateTime.Now.ToString("HH:mm:ss.ffffff")}");
Output:
Before EXE 15:11:13.482163
Name 1
Process This Started!!!
Command 1 15:11:13.533112
Process This End!!!
Name 2
Process This Started!!!
Command 2 15:11:13.563431
Process This End!!!
AFTER EXE 15:11:13.578287
I Also Tried:
Thread thr = new Thread(new ThreadStart(() => {
Console.WriteLine($"Before EXE {DateTime.Now.ToString("HH:mm:ss.ffffff")}");
foreach (var item in commands)
{
Console.WriteLine(item.Name);
item.ProcessThis();
}
Console.WriteLine($"AFTER EXE {DateTime.Now.ToString("HH:mm:ss.ffffff")}");
}));
thr.Start();
Related
I want to clean usb using diskpart from C#. I have written below code in C# to get all connect USB. And I iterate thru all usb and clean each usb using diskpart below cmd command.
diskpart
list disk
select disk <0/1/2>
clean
I want to get disk number <0/1/2> from drive name so that I can clean each usb one after another.
foreach (DriveInfo drive in DriveInfo.GetDrives())
{
if (drive.IsReady == true)
{
if (drive.DriveType == DriveType.Removable)
{
string usbName = drive.Name;
}
}
}
The following shows how to use ManagementObjectSearcher, ManagementObject to retrieve a list of removable USB drives
Create a Windows Forms App (.NET Framework) project
Add Reference (System.Management)
VS 2022:
Click Project
Select Add Reference...
Click Assemblies
Check System.Management
Click OK
Add using directives
using System.IO;
using System.Management;
using System.Diagnostics;
Create a class (name: LogicalDiskInfo)
public class LogicalDiskInfo : IComparable<LogicalDiskInfo>
{
public string Description { get; set; }
public string DeviceID { get; set; }
public uint DiskIndex { get; set; }
public uint DriveType { get; set; }
public string FileSystem { get; set; }
public bool IsRemovable { get; set; } = false;
public string Name { get; set; }
public uint PartitionIndex { get; set; }
public uint PartitionNumber { get; set; }
public UInt64 Size { get; set; }
public int CompareTo(LogicalDiskInfo other)
{
if (String.Compare(this.Name, other.Name) == 0)
return 0;
else if (String.Compare(this.Name, other.Name) < 0)
return -1;
else
return 1;
}
}
Create a class (name: LogicalDisk)
public class LogicalDisk
{
public List<LogicalDiskInfo> LogicalDiskInfos = new List<LogicalDiskInfo>();
}
Create a class (name: DiskDriveInfo)
public class DiskDriveInfo : IComparable<DiskDriveInfo>
{
public string Caption { get; set; } = string.Empty;
public string DeviceID { get; set; } = string.Empty;
public List<LogicalDiskInfo> LogicalDisks { get; set; } = new List<LogicalDiskInfo>();
public UInt32 DiskIndex { get; set; } = 0;
public string InterfaceType { get; set; } = string.Empty;
public bool IsRemovable { get; set; } = false;
public string MediaType { get; set; }
public string Model { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public UInt32 Partitions { get; set; } = 0;
public string PnpDeviceID { get; set; } = string.Empty;
public UInt64 Size { get; set; } = 0;
public string Status { get; set; } = string.Empty;
public int CompareTo(DiskDriveInfo other)
{
if (this.DiskIndex == other.DiskIndex)
return 0;
else if (this.DiskIndex < other.DiskIndex)
return -1;
else
return 1;
}
}
GetUSBRemovableDiskDriveInfo:
Note: In Windows 10, it's possible to create multiple partitions on a USB flash drive. See here for more info. Therefore, it's possible that more than one drive letter may exist on the same physical disk drive. The code below works with USB drives having either a single partition or multiple partitions.
private List<DiskDriveInfo> GetUSBRemovableDiskDriveInfo()
{
SortedDictionary<uint, DiskDriveInfo> diskDict = new SortedDictionary<uint, DiskDriveInfo>();
List<DiskDriveInfo> driveInfos = new List<DiskDriveInfo>();
//MediaType: 'Removable Media'
using (ManagementObjectSearcher searcherDiskDrive = new ManagementObjectSearcher("SELECT Caption, DeviceID, Index, InterfaceType, MediaType, Model, Name, Partitions, PNPDeviceID, Size, Status FROM Win32_DiskDrive WHERE InterfaceType='USB' and MediaType='Removable Media'"))
{
foreach (ManagementObject objDiskDrive in searcherDiskDrive.Get())
{
if (objDiskDrive == null)
continue;
//create new instance
DiskDriveInfo ddInfo = new DiskDriveInfo();
//set value
uint diskIndex = Convert.ToUInt32(objDiskDrive["Index"]);
ddInfo.Caption = objDiskDrive["Caption"]?.ToString();
ddInfo.DeviceID = objDiskDrive["DeviceID"]?.ToString();
ddInfo.DiskIndex = diskIndex;
ddInfo.InterfaceType = objDiskDrive["InterfaceType"]?.ToString();
ddInfo.MediaType = objDiskDrive["MediaType"]?.ToString();
ddInfo.Model = objDiskDrive["Model"]?.ToString();
ddInfo.Name = objDiskDrive["Name"]?.ToString();
ddInfo.Partitions = Convert.ToUInt32(objDiskDrive["Partitions"]);
ddInfo.PnpDeviceID = objDiskDrive["PnpDeviceID"]?.ToString();
ddInfo.Size = Convert.ToUInt64(objDiskDrive["Size"]);
ddInfo.Status = objDiskDrive["Status"]?.ToString();
if (ddInfo.MediaType == "Removable Media")
ddInfo.IsRemovable = true;
else
ddInfo.IsRemovable = false;
if (!diskDict.ContainsKey(diskIndex))
{
//Debug.WriteLine($"Adding DiskIndex {ddInfo.DiskIndex} Partitions: {ddInfo.Partitions}");
//add
diskDict.Add(diskIndex, ddInfo);
}
}
}
//create new instance
SortedDictionary<string, LogicalDisk> logicalDiskToPartitionDict = new SortedDictionary<string, LogicalDisk>();
//get info from Win32_LogicalDiskToPartition
//this is used to associate a DiskIndex and PartitionIndex with a drive letter
using (ManagementObjectSearcher searcherLogicalDiskToPartition = new ManagementObjectSearcher($#"SELECT * FROM Win32_LogicalDiskToPartition"))
{
foreach (ManagementObject objLogicalDiskToPartition in searcherLogicalDiskToPartition.Get())
{
if (objLogicalDiskToPartition == null)
continue;
string antecedent = objLogicalDiskToPartition["Antecedent"]?.ToString();
string dependent = objLogicalDiskToPartition["Dependent"]?.ToString();
string antecedentValue = antecedent.Substring(antecedent.IndexOf('=') + 1).Replace("\"", "");
uint diskIndex = 0;
uint partitionIndex = 0;
//get disk index and convert to uint
UInt32.TryParse(antecedentValue.Substring(antecedentValue.IndexOf("#") + 1, antecedentValue.IndexOf(",") - (antecedentValue.IndexOf("#") + 1)), out diskIndex);
//get partition index and convert to uint
UInt32.TryParse(antecedentValue.Substring(antecedentValue.LastIndexOf("#") + 1), out partitionIndex);
string driveLetter = dependent.Substring(dependent.IndexOf("=") + 1).Replace("\"", "");
if (diskDict.ContainsKey(diskIndex))
{
if (!logicalDiskToPartitionDict.ContainsKey(driveLetter))
{
//add
logicalDiskToPartitionDict.Add(driveLetter, new LogicalDisk());
}
//get info from Win32_LogicalDisk
using (ManagementObjectSearcher searcherLogicalDisk = new ManagementObjectSearcher($"SELECT Description, DeviceID, DriveType, FileSystem, Name, Size FROM Win32_LogicalDisk WHERE Name = '{driveLetter}'"))
{
foreach (ManagementObject objLogicalDisk in searcherLogicalDisk.Get())
{
if (objLogicalDisk == null)
continue;
//create new instance
LogicalDiskInfo logicalDiskInfo = new LogicalDiskInfo();
//set value
logicalDiskInfo.Description = objLogicalDisk["Description"]?.ToString();
logicalDiskInfo.DeviceID = objLogicalDisk["DeviceID"]?.ToString();
logicalDiskInfo.DriveType = Convert.ToUInt32(objLogicalDisk["DriveType"]);
logicalDiskInfo.DiskIndex = diskIndex;
logicalDiskInfo.FileSystem = objLogicalDisk["FileSystem"]?.ToString();
logicalDiskInfo.Name = objLogicalDisk["Name"]?.ToString();
logicalDiskInfo.PartitionIndex = partitionIndex;
logicalDiskInfo.PartitionNumber = partitionIndex + 1; //diskpart partitions start at 1
logicalDiskInfo.Size = Convert.ToUInt64(objLogicalDisk["Size"]);
//DriveType: 2=Removable; 3=Local Disk; 4=Network Drive; 5=CD
if (logicalDiskInfo.DriveType == 2)
logicalDiskInfo.IsRemovable = true;
else
logicalDiskInfo.IsRemovable = false;
Debug.WriteLine($"adding logicalDiskInfo for DiskIndex: '{diskIndex}' PartitionIndex: '{partitionIndex}' PartitionNumber: '{logicalDiskInfo.PartitionNumber}'");
//add
logicalDiskToPartitionDict[driveLetter].LogicalDiskInfos.Add(logicalDiskInfo);
}
}
}
}
}
//add logical disk info to disk dictionary
foreach(KeyValuePair<string, LogicalDisk> kvp in logicalDiskToPartitionDict)
{
List<LogicalDiskInfo> logicalDiskInfoList = kvp.Value.LogicalDiskInfos;
//sort
logicalDiskInfoList.Sort();
foreach (LogicalDiskInfo ldInfo in logicalDiskInfoList)
{
//add
diskDict[ldInfo.DiskIndex].LogicalDisks.Add(ldInfo);
}
}
//only add disks that are listed as 'Removable'
foreach(KeyValuePair<uint, DiskDriveInfo> kvp in diskDict)
{
if (kvp.Value.IsRemovable)
{
//add
driveInfos.Add(kvp.Value);
}
}
return driveInfos;
}
Usage:
System.Diagnostics.Debug.WriteLine("--------GetUSBRemovableDiskDriveInfo----------");
foreach (DiskDriveInfo ddInfo in GetUSBRemovableDiskDriveInfo())
{
string driveLetters = string.Empty;
for (int i = 0; i < ddInfo.LogicalDisks.Count; i++)
{
if (i > 0)
driveLetters += ", ";
driveLetters += ddInfo.LogicalDisks[i].Name;
}
System.Diagnostics.Debug.WriteLine($"Caption: '{ddInfo.Caption}' Name: '{ddInfo.Name}' DiskIndex: '{ddInfo.DiskIndex}' DriveLetters: [{driveLetters}] Partitions: '{ddInfo.Partitions}' Size: '{ddInfo.Size}'");
}
One can use System.Diagnostics.Process to execute a diskpart script to clean one or more disks. See this post for more info.
Resources:
System.Management.ManagementObjectSearcher
System.Management.ManagementObject
System.Diagnostics.Process
diskpart
diskpart scripts and examples
Win32_DiskDrive class
Win32_LogicalDisk class
Win32_LogicalDiskToPartition class
It may be the dirty way, but you could just use diskpart interactively and send commands / parse output. Since we are invoking diskpart anyway, a 100% managed codebase seems to not be the target as much as a way of getting it done.
So emphasis on "Dirty" way, but very simple functional way as well :-)
Sometimes when there is a perfectly good tool for the job such as ghostscript or ffmpeg, I automate them this way. Just as if I were typing at the CMD prompt.
Wrap a CMD instance in a wrapper to read/write to, and power it in event driven logic from there.
using System;
using System.Diagnostics;
namespace yourapp
{
public class cmdShell
{
private Process shellProcess;
public delegate void onDataHandler(cmdShell sender, string e);
public event onDataHandler onData;
public cmdShell()
{
try
{
shellProcess = new Process();
ProcessStartInfo si = new ProcessStartInfo("cmd.exe");
si.Arguments = "/k";
si.RedirectStandardInput = true;
si.RedirectStandardOutput = true;
si.RedirectStandardError = true;
si.UseShellExecute = false;
si.CreateNoWindow = true;
si.WorkingDirectory = Environment.GetEnvironmentVariable("windir");
shellProcess.StartInfo = si;
shellProcess.OutputDataReceived += shellProcess_OutputDataReceived;
shellProcess.ErrorDataReceived += shellProcess_ErrorDataReceived;
shellProcess.Start();
shellProcess.BeginErrorReadLine();
shellProcess.BeginOutputReadLine();
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
}
void shellProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
doOnData(e.Data);
}
void shellProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
doOnData(e.Data);
}
private void doOnData(string data)
{
if (onData != null) onData(this, data);
}
public void write(string data)
{
try
{
shellProcess.StandardInput.WriteLine(data);
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
}
}
}
I have this working method which I would like to write NUnit Test Case method for it. This is a console project which means the error message will be printed through Console.WriteLine method which I have the PrintMessage method to do so in Utility class. The second parameter is to control the Console.Color (red for error messaage) with boolean.
public void PlaceDeposit(BankAccount account, decimal _transaction_amt)
{
if (_transaction_amt <= 0)
Utility.PrintMessage("Amount needs to be more than zero. Try again.", false);
else if (_transaction_amt % 10 != 0)
Utility.PrintMessage($"Key in the deposit amount only with multiply of 10. Try again.", false);
else if (!PreviewBankNotesCount(_transaction_amt))
Utility.PrintMessage($"You have cancelled your action.", false);
else
{
// Bind transaction_amt to Transaction object
// Add transaction record - Start
var transaction = new Transaction()
{
AccountID = account.Id,
BankAccountNoTo = account.AccountNumber,
TransactionType = TransactionType.Deposit,
TransactionAmount = _transaction_amt,
TransactionDate = DateTime.Now
};
repoTransaction.InsertTransaction(transaction);
// Add transaction record - End
account.Balance = account.Balance + _transaction_amt;
ctx.SaveChanges();
Utility.PrintMessage($"You have successfully deposited {Utility.FormatAmount(_transaction_amt)}", true);
}
}
I have created another NUnit Test Project to test the above method which I got stuck with the Assert. Should I modify the above method to return string (method output message) in order to create NUnit Test Case or should I go about with changing my original method?
[TestFixture]
public class TestATMCustomer
{
[TestCase]
public void PlaceDeposit()
{
// Arrange
BankAccount bankAccount = new BankAccount() {
FullName = "John", AccountNumber=333111, CardNumber = 123, PinCode = 111111, Balance = 2300.00m, isLocked = false
};
decimal transactionAmount = 120;
var atmCustomer = new MeybankATM();
// Act
// Act and Assert
Assert.AreEqual(atmCustomer.PlaceDeposit(bankAccount, transactionAmount));
}
}
Updated Test Case but with error in MeybankATM constructor
// Arrange - Start
var mock = new MockMessagePrinter();
MeybankATM atmCustomer = new MeybankATM(new RepoBankAccount(), new RepoTransaction(), mock);
BankAccount bankAccount = new BankAccount()
{
FullName = "John",
AccountNumber = 333111,
CardNumber = 123,
PinCode = 111111,
Balance = 2000.00m,
isLocked = false
};
decimal transactionAmount = 0;
// Arrange - End
// Act
atmCustomer.PlaceDeposit(bankAccount, transactionAmount);
// Assert
var expectedMessage = "Amount needs to be more than zero. Try again.";
Assert.AreEqual(expectedMessage, mock.Message);
class Program
{
static void Main(string[] args)
{
var mock = new MockMessagePrinter();
ATMCustomer atmCustomer = new ATMCustomer(mock, new RepoTransaction());
atmCustomer.PlaceDeposit(new BankAccount(), 0);
Console.WriteLine(mock.Message == "Amount needs to be more than zero. Try again.");
Console.ReadLine();
}
}
public class ATMCustomer
{
private readonly IMessagePrinter _msgPrinter;
private readonly IRepoTransaction _repoTransaction;
public ATMCustomer(IMessagePrinter msgPrinter, IRepoTransaction repoTransaction)
{
_msgPrinter = msgPrinter;
_repoTransaction = repoTransaction;
}
public void PlaceDeposit(BankAccount account, decimal _transaction_amt)
{
if (_transaction_amt <= 0)
_msgPrinter.PrintMessage("Amount needs to be more than zero. Try again.", false);
else if (_transaction_amt % 10 != 0)
_msgPrinter.PrintMessage($"Key in the deposit amount only with multiply of 10. Try again.", false);
else if (!PreviewBankNotesCount(_transaction_amt))
_msgPrinter.PrintMessage($"You have cancelled your action.", false);
else
{
// Bind transaction_amt to Transaction object
// Add transaction record - Start
var transaction = new Transaction()
{
AccountID = account.Id,
BankAccountNoTo = account.AccountNumber,
TransactionType = TransactionType.Deposit,
TransactionAmount = _transaction_amt,
TransactionDate = DateTime.Now
};
_repoTransaction.InsertTransaction(transaction);
// Add transaction record - End
account.Balance = account.Balance + _transaction_amt;
//ctx.SaveChanges();
//_msgPrinter.PrintMessage($"You have successfully deposited {Utility.FormatAmount(_transaction_amt)}", true);
}
}
private bool PreviewBankNotesCount(decimal transactionAmt)
{
throw new NotImplementedException();
}
}
public class MockMessagePrinter : IMessagePrinter
{
private string _message;
public string Message => _message;
public void PrintMessage(string message, bool idontKnow)
{
_message = message;
}
}
public interface IRepoTransaction
{
void InsertTransaction(Transaction transaction);
}
public class RepoTransaction : IRepoTransaction
{
public void InsertTransaction(Transaction transaction)
{
throw new NotImplementedException();
}
}
public interface IMessagePrinter
{
void PrintMessage(string message, bool iDontKnow);
}
public class BankAccount
{
public decimal Balance { get; set; }
public string AccountNumber { get; set; }
public int Id { get; set; }
}
public class Transaction
{
public int AccountID { get; set; }
public string BankAccountNoTo { get; set; }
public TransactionType TransactionType { get; set; }
public decimal TransactionAmount { get; set; }
public DateTime TransactionDate { get; set; }
}
public enum TransactionType
{
Deposit
}
I have copied your code and change some:
I refactor your code to use IMessagePrinter instead of Utility so I can inject a mock object to check what was passed on the PrintMessage method
I did not use NUnit - I just used Console project for the example
I assumed the data types/ classes used but that does not matter
I hope this helps
Edit for NUnit:
public class TestAtmCustomer
{
[Test]
public void Should_ShowZeroErrorMessage_OnPlaceDeposit_When_AmountIsZero()
{
var mock = new MockMessagePrinter();
ATMCustomer atmCustomer = new ATMCustomer(mock, new RepoTransaction());
atmCustomer.PlaceDeposit(new BankAccount(), 0);
var expectedMessage = "Amount needs to be more than zero. Try again.";
Assert.AreEqual(expectedMessage, mock.Message);
}
}
I have orders coming in from multiple threads and I want to process this data in one thread. If I understood it right, the way to do it is with ConcurrentQueue.
I had a look at SO question How to work threading with ConcurrentQueue<T>, but it did not answer my questions.
I wrote a small test application (with .NET Core 2.1) to see if I could get it to work.
This is what it should do: Make aggregates for 100 orders. There are 3 aggregates for 3 different order types: Type1, Type2 and Type3
The output should be something like:
Type: Type1 Count: 38
Type: Type2 Count: 31
Type: Type3 Count: 31
Total for all types: 100
I started of writing the application without ConcurrentQueue. As exepected, the results in _aggregates are wrong.
/* Incorrect version, not using ConcurrentQueue, showing incorrect results */
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
namespace ConcurrentQueue
{
class Program
{
private static readonly int OrderCount = 100;
private static IEnumerable<Order> _orders;
private static Dictionary<OrderTypeEnum, Aggregate> _aggregates;
static void Main(string[] args)
{
//Prepare
InitializeAggregates();
_orders = CreateOrders();
//Execute
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
await Task.Run(() => ProcessOrders());
ShowOutput();
}
public static void ProcessOrders()
{
var aggregator = new Aggregator();
Parallel.ForEach(_orders, order => {
aggregator.Aggregate(order, _aggregates);
});
}
private static IEnumerable<Order> CreateOrders()
{
var orderList = new Collection<Order>();
for (var i = 1; i <= OrderCount; i++)
{
var order = CreateOrder(i);
orderList.Add(order);
}
return orderList;
}
private static void InitializeAggregates()
{
_aggregates = new Dictionary<OrderTypeEnum, Aggregate>();
_aggregates[OrderTypeEnum.Type1] = new Aggregate();
_aggregates[OrderTypeEnum.Type2] = new Aggregate();
_aggregates[OrderTypeEnum.Type3] = new Aggregate();
}
private static Order CreateOrder(int id)
{
var order = new Order() { Id = id, OrderType = GetRandomAggregtationType() };
return order;
}
private static OrderTypeEnum GetRandomAggregtationType()
{
Array values = Enum.GetValues(typeof(OrderTypeEnum));
var random = new Random();
return (OrderTypeEnum)values.GetValue(random.Next(values.Length));
}
private static void ShowOutput()
{
Console.WriteLine($"Type: {OrderTypeEnum.Type1} Count: {_aggregates[OrderTypeEnum.Type1].Count}");
Console.WriteLine($"Type: {OrderTypeEnum.Type2} Count: {_aggregates[OrderTypeEnum.Type2].Count}");
Console.WriteLine($"Type: {OrderTypeEnum.Type3} Count: {_aggregates[OrderTypeEnum.Type3].Count}");
var total =
_aggregates[OrderTypeEnum.Type1].Count +
_aggregates[OrderTypeEnum.Type2].Count +
_aggregates[OrderTypeEnum.Type3].Count;
Console.WriteLine($"Total for all types: {total}");
Console.ReadKey();
}
}
public class Order
{
public int Id { get; set; }
public OrderTypeEnum OrderType { get; set; }
}
public class Aggregator
{
public void Aggregate(Order order, Dictionary<OrderTypeEnum, Aggregate> aggregates)
{
aggregates[order.OrderType].Count++;
}
}
public class Aggregate
{
public int Count { get; set; }
}
public enum OrderTypeEnum
{
Type1 = 1,
Type2 = 2,
Type3 = 3
}
}
So I rewrote the application using ConcurrentQueue. The results are correct now, but I have got the feeling I am doing it wrong or it can be done more efficiently.
/* improved version using ConcurrentQueue, showing correct results */
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
namespace ConcurrentQueue
{
class Program
{
private static readonly int OrderCount = 100;
private static IEnumerable<Order> _orders;
private static Dictionary<OrderTypeEnum, Aggregate> _aggregates;
static void Main(string[] args)
{
//Prepare
InitializeAggregates();
_orders = CreateOrders();
//Execute
var proxy = new OrderProxy();
var ordersQueue = new ConcurrentQueue<OrderResult>();
Parallel.ForEach(_orders, order => {
var orderResult = proxy.PlaceOrder(order);
ordersQueue.Enqueue(orderResult);
});
foreach (var order in ordersQueue)
{
_aggregates[order.OrderType].Count++;
}
ShowOutput();
}
private static IEnumerable<Order> CreateOrders()
{
var orderList = new Collection<Order>();
for (var i = 1; i <= OrderCount; i++)
{
var order = CreateOrder(i);
orderList.Add(order);
}
return orderList;
}
private static void InitializeAggregates()
{
_aggregates = new Dictionary<OrderTypeEnum, Aggregate>();
_aggregates[OrderTypeEnum.Type1] = new Aggregate();
_aggregates[OrderTypeEnum.Type2] = new Aggregate();
_aggregates[OrderTypeEnum.Type3] = new Aggregate();
}
private static Order CreateOrder(int id)
{
var order = new Order() { Id = id, AggregateType = GetRandomAggregtationType() };
return order;
}
private static OrderTypeEnum GetRandomAggregtationType()
{
Array values = Enum.GetValues(typeof(OrderTypeEnum));
var random = new Random();
return (OrderTypeEnum)values.GetValue(random.Next(values.Length));
}
private static void ShowOutput()
{
Console.WriteLine($"Type: {OrderTypeEnum.Type1} Count: {_aggregates[OrderTypeEnum.Type1].Count}");
Console.WriteLine($"Type: {OrderTypeEnum.Type2} Count: {_aggregates[OrderTypeEnum.Type2].Count}");
Console.WriteLine($"Type: {OrderTypeEnum.Type3} Count: {_aggregates[OrderTypeEnum.Type3].Count}");
var total =
_aggregates[OrderTypeEnum.Type1].Count +
_aggregates[OrderTypeEnum.Type2].Count +
_aggregates[OrderTypeEnum.Type3].Count;
Console.WriteLine($"Total for all types: {total}");
Console.ReadKey();
}
}
public class Order
{
public int Id { get; set; }
public OrderTypeEnum AggregateType { get; set; }
}
public class OrderResult
{
public int Id { get; set; }
public OrderTypeEnum OrderType { get; set; }
}
public class OrderProxy
{
public OrderResult PlaceOrder(Order order)
{
var orderResult = new OrderResult() { Id = order.Id, OrderType = order.AggregateType };
return orderResult;
}
}
public class Aggregate
{
public OrderTypeEnum OrderType { get; set; }
public int Count { get; set; }
}
public enum OrderTypeEnum
{
Type1 = 1,
Type2 = 2,
Type3 = 3
}
}
As you see, I add objects of type OrderResult to ConcurrentQueue. I shouldn't need to use a class OrderResult. Of course I could just add the order to the queue, and iterate throught them and calculate the sums after I am finished retrieving data. Is that what I should do?
I simply want to handle the incoming orders, and simply count the different type of orders right away and store them in my 'aggregates collection'. Is that possible? If yes, how?
As suggested by David Fowler himself, I tried to use the System.Threading.Channels to solve my problem and I was able to come up with something which seems to work correctly.
Library System.Threading.Channels is poorly documented, so I hope what I did is the way it is supposed to be done.
using System;
using System.Threading.Tasks;
using System.Threading.Channels;
using System.Threading;
using System.Collections.Generic;
namespace ConcurrentQueue
{
class Program
{
//Buffer for writing. After the capacity has been reached, a read must take place because the channel is full.
private static readonly int Capacity = 10;
//Number of orders to write by each writer. (Choose 0 for infinitive.)
private static readonly int NumberOfOrdersToWrite = 25;
//Delay in ms used
private static readonly int Delay = 50;
private static Dictionary<OrderTypeEnum, Aggregate> _aggregates;
static void Main(string[] args)
{
//Prepare
InitializeAggregates();
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
var channel = Channel.CreateBounded<Order>(Capacity);
var readerTask = Task.Run(() => ReadFromChannelAsync(channel.Reader));
var writerTask01 = Task.Run(() => WriteToChannelAsync(channel.Writer, 1, NumberOfOrdersToWrite));
var writerTask02 = Task.Run(() => WriteToChannelAsync(channel.Writer, 2, NumberOfOrdersToWrite));
var writerTask03 = Task.Run(() => WriteToChannelAsync(channel.Writer, 3, NumberOfOrdersToWrite));
var writerTask04 = Task.Run(() => WriteToChannelAsync(channel.Writer, 4, NumberOfOrdersToWrite));
while (!writerTask01.IsCompleted || !writerTask02.IsCompleted || !writerTask03.IsCompleted || !writerTask04.IsCompleted)
{
}
channel.Writer.Complete();
await channel.Reader.Completion;
ShowOutput();
}
public static async Task WriteToChannelAsync(ChannelWriter<Order> writer, int writerNumber, int numberOfOrdersToWrite = 0)
{
int i = 1;
while (numberOfOrdersToWrite == 0 || i <= numberOfOrdersToWrite)
{
var order = CreateOrder(writerNumber, i);
await writer.WriteAsync(order);
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: writer {writerNumber} just wrote order {order.OrderNumber} with value {order.OrderType}.");
i++;
//await Task.Delay(Delay); //this simulates some work...
}
}
private static async Task ReadFromChannelAsync(ChannelReader<Order> reader)
{
while (await reader.WaitToReadAsync())
{
while (reader.TryRead(out Order order))
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: reader just read order {order.OrderNumber} with value {order.OrderType}.");
_aggregates[order.OrderType].Count++;
await Task.Delay(Delay); //this simulates some work...
}
}
}
private static void InitializeAggregates()
{
_aggregates = new Dictionary<OrderTypeEnum, Aggregate>();
_aggregates[OrderTypeEnum.Type1] = new Aggregate();
_aggregates[OrderTypeEnum.Type2] = new Aggregate();
_aggregates[OrderTypeEnum.Type3] = new Aggregate();
}
private static Order CreateOrder(int writerNumber, int seq)
{
string orderNumber = $"{writerNumber}-{seq}";
var order = new Order() { OrderNumber = orderNumber, OrderType = GetRandomOrderType() };
return order;
}
private static OrderTypeEnum GetRandomOrderType()
{
Array values = Enum.GetValues(typeof(OrderTypeEnum));
var random = new Random();
return (OrderTypeEnum)values.GetValue(random.Next(values.Length));
}
private static void ShowOutput()
{
var total =
_aggregates[OrderTypeEnum.Type1].Count +
_aggregates[OrderTypeEnum.Type2].Count +
_aggregates[OrderTypeEnum.Type3].Count;
Console.WriteLine();
Console.WriteLine($"Type: {OrderTypeEnum.Type1} Count: {_aggregates[OrderTypeEnum.Type1].Count}");
Console.WriteLine($"Type: {OrderTypeEnum.Type2} Count: {_aggregates[OrderTypeEnum.Type2].Count}");
Console.WriteLine($"Type: {OrderTypeEnum.Type3} Count: {_aggregates[OrderTypeEnum.Type3].Count}");
Console.WriteLine($"Total for all types: {total}");
Console.WriteLine();
Console.WriteLine("Done! Press a key to close the window.");
Console.ReadKey();
}
}
public class Order
{
public string OrderNumber { get; set; }
public OrderTypeEnum OrderType { get; set; }
}
public class Aggregator
{
public void Aggregate(Order order, Dictionary<OrderTypeEnum, Aggregate> aggregates)
{
aggregates[order.OrderType].Count++;
}
}
public class Aggregate
{
public OrderTypeEnum OrderType { get; set; }
public int Count { get; set; }
}
public enum OrderTypeEnum
{
Type1 = 1,
Type2 = 2,
Type3 = 3
}
}
I do not like the way I check the completion of the writers. How to improve this?
while (!writerTask01.IsCompleted || !writerTask02.IsCompleted ||
!writerTask03.IsCompleted || !writerTask04.IsCompleted)
{
}
Any feedback is highly appreciated.
You second solution using the ConcurrentQueue<T> isn't actually doing the aggregation concurrently. It's only adding items to the queue concurrently and then processing the queue sequentially. For this specific example code, the simplest solution would be to use the first solution you came up with, except with a lock around the increment in Aggregator.Aggregate method, like this:
public class Aggregator
{
public void Aggregate(Order order, Dictionary<OrderTypeEnum, Aggregate> aggregates)
{
var aggregate = aggregates[order.OrderType];
Interlocked.Increment(ref aggregate.Count);
}
}
As suggested by Shayne, using a lock statement (in my first code example) does work:
public class Aggregator
{
private static readonly Object _lockObj = new Object();
public void Aggregate(Order order, Dictionary<OrderTypeEnum, Aggregate> aggregates)
{
lock (_lockObj)
{
aggregates[order.OrderType].Count++;
}
}
}
I think DataFlow and System.Threading.Channels are more flexible and more elegant solutions.
interface Nameable
{
string Name { get; set; }
}
class Parent : Nameable
{
public string Name { get; set; }
public List<Child> Children { get; set; } = new List<Child>();
}
class Child
{
public string Name { get; set; }
public int Value { get; set; }
public string DataOne { get; set; }
public string DataTwo { get; set; }
public double DataThree { get; set; }
}
static async void MainAsync(string[] args)
{
for (int i = 0; i < random.Next(10000, 50000); i++)
{
Parents.Add(CreateParent());
}
Parents = Parents.GroupBy(g => g.Name).Select(grp => grp.First()).ToList();
foreach (var parent in Parents)
{
await Insert<Parent>(parent);
}
// update objects randomly;
foreach (var parent in Parents)
{
for (int i = 0; i < random.Next(10, 30); i++)
{
int decision = random.Next(0, 2);
if (decision == 0 && parent.Children.Count > 0)
{
parent.Children.RemoveAt(random.Next(0, parent.Children.Count));
}
else
{
var inner = CreateChild();
if (!parent.Children.Any(io => io.Name == inner.Name))
{
parent.Children.Add(inner);
}
}
await ReplaceOne<Parent>(parent);
}
}
}
I have a list of Parents and each one contains a list of Child elements. When using the c# Mongo driver to replace these parents after they have been updated by either removing or adding new Children It sometimes creates duplicates of the Child on the Mongo side despite there being no duplicates when the code calls the replace method.
I think this is something to do with the atomic sub document structure of Mongo and how it updates/replaces items. Is there a way to prevent this from creating duplicates? and if it is not happening due to the atomic nature what is causing this?
Edit:
static async Task ReplaceOne<T>(T obj)
where T : Nameable
{
await database.GetCollection<T>(typeof(T).Name).ReplaceOneAsync(Builders<T>.Filter.Where(t => t.Name == obj.Name), obj);
}
static async Task Insert<T>(T obj)
{
await database.GetCollection<T>(typeof(T).Name).InsertOneAsync(obj);
}
static Parent CreateParent()
{
var innerObjects = new List<Child>();
for (int i = 0; i > random.Next(1, 10); i++)
{
innerObjects.Add(CreateChild());
}
return new Parent()
{
Name = RandomString(),
Children = innerObjects
};
}
static Child CreateChild()
{
return new Child()
{
Name = RandomString(),
Value = RandomInt(),
DataOne = RandomString(),
DataTwo = RandomString(),
DataThree = RandomDouble()
};
}
Added the replace/Insert snippets, they are using the mongo c# driver to insert into the db. The CreateParent and CreateChild just fills the objects with random relevant data.
I tried to guess your RandomString(), RandomInt() and RandomDouble() methods and I ran your project several times without cleaning the database. I could not detect any duplicates whatsoever based on the two "Name" properties (on parent and child).
I suspect your observation is somehow incorrect. In order to check if you do actually have duplicate children within the same parent you can use the following query:
collection.aggregate(
{
$unwind: "$Children"
},
{
$group:
{
_id:
{
"Name": "$Name",
"ChildName": "$Children.Name"
}
, "count": { $sum: 1 }
}
},
{
$match:
{
"count": { $ne: 1 } }
}
)
I'm developing a game server for a game client. The game's character has an inventory, like any other game. The inventory has 4 tabs:
Equipment, Potions, Extra, Cash
Currently, the CharacterItems class is a List<Item>, where each Item object contains a Slot property which indicates the position of the item inside the window (the window is 4 by 4).
I don't really like this approach as it doesn't really indicate where each item is located, where with a dictionary I can have keys for each tab and then organize the item. However, I also need to index the items with a slot. So how can I use this approach with a dictionary? Wouldn't I need two dictionaries like so? Dictionary<InventoryTab, Dicitionary<int, Item>>, where InventoryTab is a certain tab and the keyed collection is indexed by the item's slot? Seems too cluttered, perhaps there is a better appraoch for this?
Also, the character also has wearable equipment on it. So that means I have to create another definition in the dictionary for equipped tab, which kinda ruins the logic.
Here's a test for the apparoach I mentioned before:
using System;
using System.Collections.Generic;
using System.Linq;
namespace InventoryTest
{
class Program
{
static void Main(string[] args)
{
var inventory = new Inventory();
while (true)
{
var mapleId = new Random().Next(1000000, 5999999);
short str = 0;
short dex = 0;
short intt = 0;
short luk = 0;
if (mapleId >= 1000000 && mapleId <= 1999999)
{
str = (short)new Random().Next(0, 5);
dex = (short)new Random().Next(0, 5);
intt = (short)new Random().Next(0, 5);
luk = (short)new Random().Next(0, 5);
}
var item = new Item()
{
MapleId = mapleId,
Str = str,
Dex = dex,
Int = intt,
Luk = luk
};
inventory.Add(item);
Console.ReadLine();
}
}
}
class Item
{
public int MapleId { get; set; }
public short Str { get; set; }
public short Dex { get; set; }
public short Int { get; set; }
public short Luk { get; set; }
public Tab Tab
{
get
{
return (Tab)(this.MapleId / 1000000);
}
}
}
enum Tab : byte
{
Equip = 1,
Use,
Setup,
Etc,
Cash
}
class Inventory : Dictionary<Tab, Dictionary<sbyte, Item>>
{
public Inventory()
: base()
{
foreach (var tab in Enum.GetValues(typeof(Tab)).Cast<Tab>())
{
base.Add(tab, new Dictionary<sbyte, Item>(96));
}
}
public void Add(Item item)
{
var nextFreeSlot = this.GetNextFreeSlot(item.Tab);
this[item.Tab].Add(nextFreeSlot, item);
Console.WriteLine("Added new item to {0} tab. Info:", item.Tab);
Console.WriteLine("Slot: {0}", nextFreeSlot);
Console.WriteLine("MapleId: {0}", item.MapleId);
if (item.Str > 0)
{
Console.WriteLine("Str: {0}", item.Str);
}
if (item.Dex > 0)
{
Console.WriteLine("Dex: {0}", item.Dex);
}
if (item.Int > 0)
{
Console.WriteLine("Int: {0}", item.Int);
}
if (item.Luk > 0)
{
Console.WriteLine("Luk: {0}", item.Luk);
}
}
private sbyte GetNextFreeSlot(Tab tab)
{
sbyte slot = 0;
foreach (var item in this[tab])
{
if (item.Key == slot)
{
slot += 1;
}
else
{
break;
}
}
return slot;
}
}
}