I have two methods from two different third party libraries:
Task WriteToAsync(Stream stream);
Task LoadAsync(Stream stream);
I need to pipe data from source WriteTo method to Load method.
Currently next solution is used:
using (var stream = new MemoryStream()) {
await source.WriteToAsync(stream);
stream.Position = 0;
await destination.LoadAsync(stream);
}
Is there any better way?
As the code below demonstrates, you can use pipe streams to stream data from one to the other, and you should not use await on the writer until after you have started the reader.
class Program
{
static void Main(string[] args)
{
ReaderDemo rd = new ReaderDemo();
GenPrimes(rd).ContinueWith((t) => {
if (t.IsFaulted)
Console.WriteLine(t.Exception.ToString());
else
Console.WriteLine(rd.value);
}).Wait();
}
static async Task GenPrimes(ReaderDemo rd)
{
using (var pout = new System.IO.Pipes.AnonymousPipeServerStream(System.IO.Pipes.PipeDirection.Out))
using (var pin = new System.IO.Pipes.AnonymousPipeClientStream(System.IO.Pipes.PipeDirection.In, pout.ClientSafePipeHandle))
{
var writeTask = WriterDemo.WriteTo(pout);
await rd.LoadFrom(pin);
await writeTask;
}
}
}
class ReaderDemo
{
public string value;
public Task LoadFrom(System.IO.Stream input)
{
return Task.Run(() =>
{
using (var r = new System.IO.StreamReader(input))
{
value = r.ReadToEnd();
}
});
}
}
class WriterDemo
{
public static Task WriteTo(System.IO.Stream output)
{
return Task.Run(() => {
using (var writer = new System.IO.StreamWriter(output))
{
writer.WriteLine("2");
for (int i = 3; i < 10000; i+=2)
{
int sqrt = ((int)Math.Sqrt(i)) + 1;
int factor;
for (factor = 3; factor <= sqrt; factor++)
{
if (i % factor == 0)
break;
}
if (factor > sqrt)
{
writer.WriteLine("{0}", i);
}
}
}
});
}
}
Related
I'm trying to write a C# application that updates a progress bar as multiple files have been copied through. I am using this method to do the copying which takes a Dictionary<string, string>:
public static async Task CopyFiles(Dictionary<string, string> files, IProgress<int> progressCallback)
{
for (var x = 0; x < files.Count; x++)
{
var item = files.ElementAt(x);
var from = item.Key;
var to = item.Value;
using (var outStream = new FileStream(to, FileMode.Create, FileAccess.Write, FileShare.Read))
{
using (var inStream = new FileStream(from, FileMode.Open, FileAccess.Read, FileShare.Read))
{
await inStream.CopyToAsync(outStream);
}
}
var percentComplete = ((x + 1) / files.Count) * 100;
//progressCallback((int)((x + 1) / files.Count) * 100);
progressCallback.Report(percentComplete);
}
}
Inside my main class I am using the following code to try to update my progress bar (progressBar1) but this code doesn't appear to be working, it seems to complete the ProgressBar towards the END of the copying of both files and it takes like a second to complete, not sure why this is happening.
private async void button4_Click(object sender, EventArgs e)
{
button4.Enabled = false;
var progress = new Progress<int>(percent =>
{
progressBar1.Value = percent;
});
await Copier.CopyFiles(new Dictionary<string, string> {
{ Source1, Destination1 },
{ Source2, Destination2 }
},
progress);
button4.Enabled = true;
MessageBox.Show("Completed");
}
}
The problem is caused because the integer division x + 1 / files.Count returns zero for every x except the last. You can fix this by multiplying by 100 first (100*(x+1)/files.Count) but there are better alternatives.
There's no need to calculate and report a percentage in CopyFiles. You can set the progress bar's Maximum value :
private async void button4_Click(object sender, EventArgs e)
{
button4.Enabled = false;
var files = new Dictionary<string, string> {
{ Source1, Destination1 },
{ Source2, Destination2 }
};
progressBar1.Maximum=files.Count;
progressBar1.Value =0;
var progress = new Progress<int>(x =>
{
progressBar1.Value = x+1;
});
await Copier.CopyFiles(files,progress);
button4.Enabled = true;
MessageBox.Show("Completed");
}
}
And report just the current iteration :
public static async Task CopyFiles(Dictionary<string, string> files, IProgress<int> progressCallback)
{
for (var x = 0; x < files.Count; x++)
{
...
progressCallback.Report(x);
}
}
IProgress< T> can handle complex objects, not just primitive types. It's possible to report both the iteration and the file names for display in the status bar :
record CopyProgress(int Iteration, string From, string To);
...
var progress = new Progress<int>(x =>
{
progressBar1.Value = x.Iteration;
statusText.Text = $"Copied {x.From} to {x.To}";
});
...
progressCallback.Report(new CopyProgress(x,from,to);
its my first time doing unit tests/integration tests and I have a question. So I started doing unit tests for my code, but I have a method, which is actually the logic of the whole application, where multiple methods are called, and user input is required. How can I test that method? Here is the method:
public async Task RunAsync()
{
var watch = System.Diagnostics.Stopwatch.StartNew();
var playAgain = 'y';
do
{
var attempts = 1;
var foundNumber = false;
while (attempts < 10 && foundNumber == false)
{
try
{
var inputNumber = int.Parse(GetInput());
if (inputNumber == _randomNumber)
{
foundNumber = true;
OnSuccesfulGuess(watch, attempts);
}
else if (attempts < 10)
{
OnWrongGuessWithinAttempts(inputNumber);
}
else
{
Console.WriteLine("Oops, maybe next time.");
}
}
catch (Exception ex)
{
Console.WriteLine("Please enter a number");
}
attempts++;
}
playAgain = PlayAgain(playAgain);
_randomNumber = await GetRandomNumber(1, 100);
Log.Information("User wants to play again");
}
while (playAgain == 'y' || playAgain == 'Y');
}
This is the method where i run in my Program class in order to start the application.
Your code is essentially untestable.
The method does too much work. It should be splitted into several smaller ones that can be tested separately.
You should get rid of static methods. Because you can't get them fake.
Getting data over the network (I see using WebSocket), as well as from the database or file system, should be brought out. You should pass ready-made data to this method.
Here is the modified code, broken down into small methods. Logging and events are removed from the code so as not to complicate the explanation.
public class App
{
private readonly Random _random = new Random();
private Task<int> GetRandomNumber(int min, int max)
{
return Task.FromResult(_random.Next(min, max));
}
internal int GetInput()
{
Console.WriteLine("Please guess a number between 1 and 100");
int value;
while (true)
{
string input = Console.ReadLine();
bool result = int.TryParse(input, out value);
if (!result)
Console.WriteLine("Not a number");
else if (value < 1 || value > 100)
Console.WriteLine("Must be between 1 and 100");
else
break;
}
return value;
}
internal bool PlayAgain()
{
Console.WriteLine("Do you want to play again?");
string input = Console.ReadLine();
return input == "Y" || input == "y";
}
internal void Guessing(int randomNumber)
{
int attempts = 1;
while (attempts < 10)
{
var inputNumber = GetInput();
// logging
if (inputNumber == randomNumber)
{
// OnSuccesfulGuess
return;
}
else
{
// OnWrongGuessWithinAttempts
}
attempts++;
}
Console.WriteLine("Oops, maybe next time.");
// logging
}
public async Task RunAsync()
{
do
{
int randomNumber = await GetRandomNumber(1, 100);
Guessing(randomNumber);
}
while (PlayAgain());
}
}
Now we have the ability to test individual methods.
I use MSTest.
[DataTestMethod]
[DataRow("Y")]
[DataRow("y")]
public void PlayAgain_InputY_ReturnsTrue(string value)
{
using (var reader = new StringReader(value))
{
Console.SetIn(reader);
var app = new App();
bool result = app.PlayAgain();
Assert.IsTrue(result);
}
}
[DataTestMethod]
[DataRow("N")]
[DataRow("boo")]
[DataRow("")]
public void PlayAgain_InputNotY_ReturnsFalse(string value)
{
using (var reader = new StringReader(value))
{
Console.SetIn(reader);
var app = new App();
bool result = app.PlayAgain();
Assert.IsFalse(result);
}
}
We do the same with the other methods.
Here are the tests for the GetInput method.
Since there is a loop inside that runs indefinitely when incorrect values are entered, we must interrupt it by entering the correct value. This is done by passing two values via a line feed: "0\n50". Entering an incorrect value is a test of the output string, then interrupting the loop with the correct value.
[DataTestMethod]
[DataRow("1")]
[DataRow("50")]
[DataRow("100")]
public void GetInput_InputCorrectString_ReturnsNumber(string value)
{
using (var reader = new StringReader(value))
{
Console.SetIn(reader);
var app = new App();
int actual = app.GetInput();
int expected = int.Parse(value);
Assert.AreEqual(expected, actual);
}
}
[DataTestMethod]
[DataRow("0\n50")]
[DataRow("101\n50")]
public void GetInput_InputSmallerOrGreaterValue_WritesMessage(string value)
{
using (var reader = new StringReader(value))
using (var writer = new StringWriter())
{
Console.SetIn(reader);
Console.SetOut(writer);
var app = new App();
_ = app.GetInput();
string actualMessage = writer.ToString();
string expectedMessage = "Must be between 1 and 100";
Assert.IsTrue(actualMessage.Contains(expectedMessage));
}
}
[DataTestMethod]
[DataRow("x\n50")]
[DataRow("qwerty\n50")]
public void GetInput_InputNotNumber_WritesMessage(string value)
{
using (var reader = new StringReader(value))
using (var writer = new StringWriter())
{
Console.SetIn(reader);
Console.SetOut(writer);
var app = new App();
_ = app.GetInput();
string actualMessage = writer.ToString();
string expectedMessage = "Not a number";
Assert.IsTrue(actualMessage.Contains(expectedMessage));
}
}
Normally the unit testing methods are made regarding the different results that may return. You can create an interface to handle this method and communicate giving values depending of the output expected (Mocking). Check this post maybe would help!:
How do I apply unit testing to C# function which requires user input dynamically?
I think I get that this is hanging because I am not awaiting the async call from Main causing a deadlock but I cant make Main async so how to fix this? I will first show the program I am trying to parallelize then I will show my attempt at parallelization. I think it is obvious I am trying to get the fastest possible program to check the size of a list of folders (from multiple shares). If it is possible to parallelize at a higher level and write the outputs to CSV out of order that is fine but I was satisfied with processing one share at a time. I have tried several derivations of the parallel code. This is just my latest so it is possible it is more wrong than my earlier attempts. Just know that this was not my only attempt. I am currently knee deep in researching parallelization in c# and will probably be able to figure this out at some point but if you can offer insights that would be greatly appreciated
namespace ShareSize
{
class Program
{
static long Size { get; set; }
static void Main(string[] args)
{
using (StreamReader sr = new StreamReader(args[0]))
{
while (!sr.EndOfStream)
{
share = sr.ReadLine().Trim(',');
Console.WriteLine(share);
string[] root = Directory.GetDirectories(share);
MeasureFolders(root);
MeasureFiles(Directory.GetFiles(share));
Console.WriteLine("SIZE = " + Size);
using (StreamWriter sw = new StreamWriter(args[1], true))
{
sw.WriteLine(share + "," + Size / 1073741824);
}
Size = 0;
}
}
Console.ReadLine();
}
private static MeasureFolders(string[] root)
{
MeasureFolder(root);
}
private static MeasureFolder(string[] directories)
{
foreach (string d in directories)
{
try
{
Console.WriteLine($"Measure Folder {d}");
string[] files = Directory.GetFiles(d);
string[] subDirectories = Directory.GetDirectories(d);
if (files.Length != 0)
MeasureFiles(files);
if (subDirectories.Length != 0)
MeasureFolder(subDirectories);
}
catch
{
;
}
}
}
private static void MeasureFiles(string[] files)
{
foreach (var f in files)
{
Size += new FileInfo(f).Length;
}
}
}
}
And here is my attempt at parallelization.
namespace ShareSize
{
class Program
{
static long Size { get; set; }
static List<Task> Tasks = new List<Task>();
private static Object Lock = new Object();
static void Main(string[] args)
{
string share = "";
using (StreamReader sr = new StreamReader(args[0]))
{
while (!sr.EndOfStream)
{
share = sr.ReadLine().Trim(',');
string[] root = Directory.GetDirectories(share);
MeasureFolders(root).ConfigureAwait(false);
MeasureFiles(Directory.GetFiles(share));
using (StreamWriter sw = new StreamWriter(args[1], true))
{
sw.WriteLine(share + "," + Size / 1073741824);
}
Size = 0;
}
}
Console.ReadLine();
}
private static async Task MeasureFolders(string[] root)
{
await MeasureFolder(root).ConfigureAwait(false);
await Task.WhenAll(Tasks.ToArray());
}
private static async Task MeasureFolder(string[] directories)
{
foreach (string d in directories)
{
try
{
string[] files = Directory.GetFiles(d);
string[] subDirectories = Directory.GetDirectories(d);
if (files.Length != 0)
{
Task newTask = new Task(delegate { MeasureFiles(files); });
newTask.Start();
Tasks.Add(newTask);
}
if (subDirectories.Length != 0)
await MeasureFolder(subDirectories);
}
catch
{
;
}
}
}
private static void MeasureFiles(string[] files)
{
foreach (var f in files)
{
lock (Lock)
{
try
{
Size += new FileInfo(f).Length;
}
catch
{
;
}
}
}
}
}
}
Thank you very much.
Maybe I am missing the point, but the above seems a bit over-complicated. The below code snippets get the directory size of any given path. I wrote the code in such a way that comparing the serialized code to the parallelized code is easier. But one of the biggest things to consider: If you are going to collect data in parallel, you will likely need to allocate memory ahead of time (array), or lock the object to ensure no concurrent access (lock() { }). Both are demonstrated below.
Notes:
This code can be optimized further
Getting the size on disk would require a few changes
This uses built-in C# Parallel.For, Parallel.Foreach, and lock() {} syntax
SerialFunctions
public long GetDirectorySizesBytes(string root) {
long dirsize = 0;
string[] directories = Directory.GetDirectories(root);
string[] files = Directory.GetFiles(root);
if (files != null) {
dirsize += GetFileSizesBytes(files);
}
foreach(var dir in directories) {
var size = GetDirectorySizesBytes(dir);
dirsize += size;
}
return dirsize;
}
public long GetFileSizesBytes(string[] files) {
long[] fileSizes = new long[files.Length];
for(int i = 0; i < files.Length; i++) {
fileSizes[i] = new FileInfo(files[i]).Length;
}
return fileSizes.Sum();
}
Parallelized Functions
public long ParallelGetDirectorySizesBytes(string root) {
long dirsize = 0;
string[] directories = Directory.GetDirectories(root);
string[] files = Directory.GetFiles(root);
if (files != null) {
dirsize += ParallelGetFileSizesBytes(files);
}
Parallel.ForEach(directories, dir => {
var size = ParallelGetDirectorySizesBytes(dir);
lock (lockObject) { //static lockObject defined at top of class
dirsize += size;
}
});
return dirsize;
}
public long ParallelGetFileSizesBytes(string[] files) {
long[] fileSizes = new long[files.Length];
Parallel.For(0, files.Length, i => {
fileSizes[i] = new FileInfo(files[i]).Length;
});
return fileSizes.Sum();
}
Test function
[TestMethod]
public void GetDirectoriesSizesTest() {
var actual = GetDirectorySizesBytes(#"C:\Exchanges");
var parallelActual = ParallelGetDirectorySizesBytes(#"C:\Exchanges");
long expected = 25769767281;
Assert.AreEqual(expected, actual);
Assert.AreEqual(expected, parallelActual);
}
Complete Class
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace StackOverflowProjects.Tests {
[TestClass]
public class DirectorySizeTests {
public static object lockObject = new object();
[TestMethod]
public void GetDirectoriesSizesTest() {
var actual = GetDirectorySizesBytes(#"C:\Exchanges");
var parallelActual = ParallelGetDirectorySizesBytes(#"C:\Exchanges");
long expected = 25769767281;
Assert.AreEqual(expected, actual);
Assert.AreEqual(expected, parallelActual);
}
public long GetDirectorySizesBytes(string root) {
long dirsize = 0;
string[] directories = Directory.GetDirectories(root);
string[] files = Directory.GetFiles(root);
if (files != null) {
dirsize += GetFileSizesBytes(files);
}
foreach(var dir in directories) {
var size = GetDirectorySizesBytes(dir);
dirsize += size;
}
return dirsize;
}
public long GetFileSizesBytes(string[] files) {
long[] fileSizes = new long[files.Length];
for(int i = 0; i < files.Length; i++) {
fileSizes[i] = new FileInfo(files[i]).Length;
}
return fileSizes.Sum();
}
public long ParallelGetDirectorySizesBytes(string root) {
long dirsize = 0;
string[] directories = Directory.GetDirectories(root);
string[] files = Directory.GetFiles(root);
if (files != null) {
dirsize += ParallelGetFileSizesBytes(files);
}
Parallel.ForEach(directories, dir => {
var size = ParallelGetDirectorySizesBytes(dir);
lock (lockObject) {
dirsize += size;
}
});
return dirsize;
}
public long ParallelGetFileSizesBytes(string[] files) {
long[] fileSizes = new long[files.Length];
Parallel.For(0, files.Length, i => {
fileSizes[i] = new FileInfo(files[i]).Length;
});
return fileSizes.Sum();
}
}
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
When I execute this code in command line, it's working fine:
class Program
{
private static List<Ping> pingers = new List<Ping>();
private static List<string> value = new List<string>();
private static int instances = 0;
private static object #lock = new object();
private static int result = 0;
private static int timeOut = 2500;
private static int ttl = 7;
public static void Main()
{
string baseIP = "192.168.1.";
Console.WriteLine("Pinging destinations of D-class in {0}*", baseIP);
CreatePingers(254);
PingOptions po = new PingOptions(ttl, true);
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
byte[] data = enc.GetBytes("");
SpinWait wait = new SpinWait();
int cnt =1;
Stopwatch watch = Stopwatch.StartNew();
foreach (Ping p in pingers)
{
lock (#lock)
{
instances += 1;
}
p.SendAsync(string.Concat(baseIP, cnt.ToString()), timeOut, data, po);
cnt += 1;
}
//while (instances > 0)
//{
// wait.SpinOnce();
//}
watch.Stop();
for (int i = 0; i < value.Count; i++)
{
Console.WriteLine(value[i]);
}
DestroyPingers();
Console.WriteLine("Finished in {0}. Found {1} active IP-addresses.", watch.Elapsed.ToString(), result);
Console.ReadKey();
}
public static void Ping_completed(object s, PingCompletedEventArgs e)
{
lock (#lock)
{
instances -= 1;
}
if (e.Reply.Status == IPStatus.Success)
{
string sa = string.Concat("Active IP: ", e.Reply.Address.ToString());
value.Add(sa);
//Console.WriteLine(sa);
String diachiip = e.Reply.Address.ToString();
result += 1;
}
else
{
//Console.WriteLine(String.Concat("Non-active IP: ", e.Reply.Address.ToString()))
}
}
private static void CreatePingers(int cnt)
{
for (int i = 1; i <= cnt; i++)
{
Ping p = new Ping();
p.PingCompleted += Ping_completed;
pingers.Add(p);
}
}
private static void DestroyPingers()
{
foreach (Ping p in pingers)
{
p.PingCompleted -= Ping_completed;
p.Dispose();
}
pingers.Clear();
}
}
But when I convert from it to window form, it doesn't work. I don't kwow why, I have tried many different ways...
Code is here:
public partial class Form1 : Form
{
public static List<Ping> pingers = new List<Ping>();
public static List<string> value = new List<string>();
public static int instances = 0;
public static object #lock = new object();
public static int result = 0;
public int timeout = 2500;
public static int ttl = 7;
public Form1()
{
InitializeComponent();
}
public void btnscan_Click(object sender, EventArgs e)
{
string baseIP = "192.168.1.";
//int kt = Int32.Parse(txtkt.Text);
//int start = Int32.Parse(txtstart.Text);
CreatePingers(254);
PingOptions po = new PingOptions(ttl, true);
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
byte[] data = enc.GetBytes("");
int cnt = 1;
Stopwatch watch = Stopwatch.StartNew();
foreach (Ping p in pingers)
{
lock (#lock)
{
instances += 1;
}
p.SendAsync(string.Concat(baseIP, cnt.ToString()), timeout, data, po);
cnt += 1;
}
watch.Stop();
//Result alway return 0
lst1.Items.Add(result.ToString());
lst1.Items.Add(value.Count.ToString());
for (int i = 0; i < value.Count; i++)
{
lst1.Items.Add(value[i]);
lst1.Items.Add("\n");
}
DestroyPingers();
string a = "Finished in " + watch.Elapsed.ToString() + ". Found " + result + " active IP-addresses.";
lst1.Items.Add(a);
}
public static void CreatePingers(int kt)
{
for (int start = 1; start <= kt; start++)
{
// class System.Net.NetworkInformation.Ping
Ping p = new Ping();
p.PingCompleted += Ping_completed();
pingers.Add(p);
}
}
public static PingCompletedEventHandler Ping_completed()
{
PingCompletedEventHandler a = new PingCompletedEventHandler(abc);
return a;
}
static void abc(object s, PingCompletedEventArgs e)
{
value.Add("abc");
lock (#lock)
{
instances -= 1;
}
if (e.Reply.Status == IPStatus.Success)
{
string abcd = string.Concat("Active IP: ", e.Reply.Address.ToString());
value.Add(abcd);
result += 1;
}
}
public static void DestroyPingers()
{
foreach (Ping p in pingers)
{
p.PingCompleted -= Ping_completed();
p.Dispose();
}
pingers.Clear();
}
}
What is wrong in this code?
Method SendAsync returns 0 because you are not waiting for it to complete. You are missing await and async (see msdn):
async void btnscan_Click(object sender, EventArgs e)
{
...
await p.SendAsync(string.Concat(baseIP, cnt.ToString()), timeout, data,
...
}
SpinWait was making code to work in console application. In winforms you should not use SpinWait (nor Sleep) in UI thread. You can create another thread (e.g. by using Task) and then you can copy/paste code from console application 1-to-1. But then you will need to use Invoke each time when you want to access UI controls.
async/await is really better.. if it will work (I concluded that from method name, I've no idea what method does, nor how to use it).
Perhaps I miss one thing, if SendAsync returns value, then you can get it by (the requirement to mark method where you use await with async still):
var result = await p.SendAsync(...);
I have written a id manager for a xml repository. The repository manages entries in the xml file and assigns unique id (integers) to each entry that is added. The same way databases auto assign new id's to entries added to a table.
The repository will be called asynchronously so I need the id manager to be thread safe. I am using the C# lock statement but it does not seem to help. My unit tests succeed in single threaded execution but fail when run in parallel ( IE: Task ). Specifically they only fail with large sets of parallel tasks above 1000+ and even only then they only fail every other time.
The exception states that it expected 10000 but got 9998. The exception is always the same having to do with 2 missing id that were not registered.
What the heck am I missing?
ID Manager code and unit tests are provided below. The id manager utilizes Linq and is thus not very performance oriented with large sets of id's. Unit Tests TestAsyncRegistration and TestAsyncRandomRegistration are the tests that throw the exceptions.
public class IdManager
{
private List<int> idList = new List<int>();
private List<int> availableList = new List<int>();
private int nextId;
private int bufferCount;
object obj = new object();
public ReadOnlyCollection<int> RegisteredIds
{
get
{
return new ReadOnlyCollection<int>(this.idList);
}
}
public int BufferCount
{
get
{
return this.bufferCount;
}
set
{
if (value < 1)
{
throw new ArgumentOutOfRangeException("value");
}
this.bufferCount = value;
}
}
public IdManager(int bufferCount)
{
this.BufferCount = bufferCount;
this.Reset();
}
public IdManager()
: this(1000)
{
}
public void RegisterId(int id)
{
this.RegisterId(new[] { id });
}
public void Reset()
{
lock (this.obj)
{
this.availableList.Clear();
this.idList.Clear();
for (var i = 0; i < this.bufferCount; i++)
{
this.availableList.Add(i);
}
}
}
public void RegisterId(IEnumerable<int> ids)
{
lock (this.obj)
{
var distinct = ids.Except(this.idList);
this.idList.AddRange(distinct);
this.availableList = this.availableList.Except(this.idList).ToList();
}
}
public int NewId()
{
lock (this.obj)
{
if (this.availableList.Count > 0)
{
var item = this.availableList[0];
this.availableList.RemoveAt(0);
this.idList.Add(item);
return item;
}
var max = this.idList.Max();
for (var i = 1; i < this.bufferCount; i++)
{
this.availableList.Add(max + i);
}
this.availableList = this.availableList.Except(this.idList).ToList();
return this.NewId();
}
}
}
... and the unit test code ...
[TestClass]
public class IdManagerTests
{
[TestMethod]
public void TestSequence()
{
var manager = new IdManager(5);
for (var i = 0; i < manager.BufferCount + 10; i++)
{
Assert.AreEqual(i, manager.NewId());
}
}
[TestMethod]
public void TestBrokenSequence()
{
var manager = new IdManager(5);
manager.RegisterId(1);
Assert.AreEqual(0, manager.NewId());
Assert.AreEqual(2, manager.NewId());
for (var i = 3; i < manager.BufferCount + 10; i++)
{
Assert.AreEqual(i, manager.NewId());
}
}
[TestMethod]
public void TestForwardSequence()
{
var manager = new IdManager(5);
manager.RegisterId(0);
manager.RegisterId(1);
manager.RegisterId(2);
Assert.AreEqual(3, manager.NewId());
Assert.AreEqual(4, manager.NewId());
for (var i = 5; i < manager.BufferCount + 10; i++)
{
Assert.AreEqual(i, manager.NewId());
}
}
[TestMethod]
public void TestBackwardSequence()
{
var manager = new IdManager(5);
manager.RegisterId(2);
manager.RegisterId(1);
manager.RegisterId(0);
Assert.AreEqual(3, manager.NewId());
Assert.AreEqual(4, manager.NewId());
for (var i = 5; i < manager.BufferCount + 10; i++)
{
Assert.AreEqual(i, manager.NewId());
}
}
[TestMethod]
public async Task TestLargeNumbersRegistration()
{
// register a list of id's from 0 to 1000
var list = new List<int>();
for (int i = 0; i < 1000; i++)
{
list.Add(i);
}
var manager = new IdManager(1000);
manager.RegisterId(list);
var taskCount = 10000;
var taskList = new Task[taskCount];
var idValue = 0;
for (int i = 0; i < taskList.Length; i++)
{
manager.RegisterId(idValue++);
}
Assert.AreEqual(taskCount, manager.NewId());
}
[TestMethod]
public async Task TestAsyncRegistration()
{
// register a list of id's from 0 to 1000
var list = new List<int>();
for (int i = 0; i < 1000; i++)
{
list.Add(i);
}
var manager = new IdManager(1000);
manager.RegisterId(list);
var taskCount = 10000;
var taskList = new Task[taskCount];
var idValue = 0;
for (int i = 0; i < taskList.Length; i++)
{
taskList[i] = Task.Factory.StartNew(() => manager.RegisterId(idValue++));
}
Task.WaitAll(taskList);
Assert.AreEqual(taskCount, manager.NewId());
}
[TestMethod]
public async Task TestAsyncRandomRegistration()
{
// register a list of id's from 0 to 1000
var list = new List<int>();
for (int i = 0; i < 1000; i++)
{
list.Add(i);
}
// randomize the order of the id's in the list
var random = new Random((int)DateTime.Now.Ticks);
var randomizedList = from item in list
orderby random.Next()
select item;
var manager = new IdManager(1000);
manager.RegisterId(randomizedList);
var taskCount = 10000;
var taskList = new Task[taskCount];
var idValue = 0;
for (int i = 0; i < taskList.Length; i++)
{
taskList[i] = Task.Factory.StartNew(() => manager.RegisterId(idValue++));
}
Task.WaitAll(taskList);
Assert.AreEqual(taskCount, manager.NewId());
}
}
Your problem is in your test, not the method that you're testing, specifically the snippet:
Task.Factory.StartNew(() => manager.RegisterId(idValue++));
You're calling idValue++ from a bunch of different threads simultaneously. That's not a safe operation to perform. Either increment idValue outside of StartNew and pass in the already incremented value, or use Interlocked.Increment to handle it safely.