How to unit test a thread safe queue - c#

I need a simple data structure with these requirements:
it should behave like a queue,
all the enqueue operations should be atomic.
I have very limited experience with multithreading, but this is what I came up to:
public class Tickets
{
private ConcurrentQueue<uint> _tickets;
public Tickets(uint from, uint to)
{
Initialize(from, to);
}
private readonly object _lock = new object();
public void Initialize(uint from, uint to)
{
lock(_lock)
{
_tickets = new ConcurrentQueue<uint>();
for (uint i = from; i <= to; i++)
{
_tickets.Enqueue(i);
}
}
}
public uint Dequeue()
{
uint number;
if (_tickets.TryDequeue(out number))
{
return number;
}
throw new ArgumentException("Ticket queue empty!");
}
}
First question: is this code ok?
Secod question: how can I unit test this class (for instance with two threads which are perfoming dequeue operation periodically on the queue with elements (1, 2, 3, 4, 5, 6) and the first thread should get only odd numbers and the second thread only the even numbers)? I tried this, but the asserts aren't executing:
[Test]
public void Test()
{
var tickets = new Tickets(1, 4);
var t1 = new Thread(() =>
{
Assert.AreEqual(1, tickets.Dequeue());
Thread.Sleep(100);
Assert.AreEqual(3, tickets.Dequeue());
});
var t2 = new Thread(() =>
{
Assert.AreEqual(2, tickets.Dequeue());
Thread.Sleep(100);
Assert.AreEqual(4, tickets.Dequeue());
});
t1.Start();
t2.Start();
}

I would use chess: http://research.microsoft.com/en-us/projects/chess
CHESS is a tool for finding and reproducing Heisenbugs in concurrent programs. CHESS repeatedly runs a concurrent test ensuring that every run takes a different interleaving. If an interleaving results in an error, CHESS can reproduce the interleaving for improved debugging. CHESS is available for both managed and native programs.

The problem with multithreading and unit tests is one of timing. When you try to introduce multiple threads to unit tests you run the risk of non-reproducable test results, tests that pass sometimes but not other times.
But just to explain why your asserts may not be executing, the unit test completes before the threads. It needs to wait for the threads to complete rather than just kicking them off and moving on. It's also feasible that the unit test framework itself is not threadsafe or capable of Asserts being called from other threads.
Sorry it's not a solution, but I don't know of any automated testing solution for multithreaded code either.
See also: How should I unit test threaded code?

Related

Multi-threading testing of Semaphore based business logic in C# / MSTEST

In my C# / Net Core project, I have a method like
var MySemaphore = new SemaphoreSlim(1, 1);
public async Task<RetrievalStatusReply> InitializeUpdate()
{
// This instruction lets routine quit if lock is taken by other thread
if (!MySemaphore.Wait(0))
{
// if can not enter the lock
return;
}
try
{
// business logic
}
finally
{
MySemaphore.Release();
}
}
and would like to test its behaviour using some kind of multi-threading setup or test utility library in an MsTest, for example, similar to Java's Concurrentunit.
How I could write such a test for C# / Net Core ?
Testing multithreaded code can be quite messy. My preferred approach is to use ManualResetEvents to control the execution order. This usually require that you have some way to instrument the code to test, by injecting the test code or some other way.
So something like this should give a consistent test result:
SemaphoreSlim MySemaphore = new SemaphoreSlim(1, 1);
public ManualResetEventSlim BusinessLogicReached= new ManualResetEventSlim(false);
public ManualResetEventSlim BusinessLogicWaiting = new ManualResetEventSlim(false);
public bool InitializeUpdate()
{
// This instruction lets routine quit if lock is taken by other thread
if (!MySemaphore.Wait(0))
{
// if can not enter the lock
return false;
}
try
{
BusinessLogicReached.Set();
BusinessLogicWaiting.Wait();
return true;
}
finally
{
MySemaphore.Release();
}
}
[Test]
public void RunTest()
{
var updateTask = Task.Run(InitializeUpdate);
BusinessLogicReached.Wait();
Assert.IsFalse(InitializeUpdate()); // Semaphore taken
BusinessLogicWaiting.Set();
updateTask.Wait();
Assert.IsTrue(updateTask.Result);
Assert.IsTrue(InitializeUpdate()); // Semaphore released
}
That said, these kinds of tests are cumbersome to write and maintain, and does not prove general thread-safety, so it might be more cost effective to do thorough code reviews of multi-threaded code.

Unit testing of thread synchronization (lock)

I'm working on multithread app and there is part of code which sholud be run only by one thread same time. Nothing complicated. I use lock to synchronize it. It's working in life system but I would like to write unit test to check if only one thread is in the critical section. I wrote one, it was working but it stops :)
I can't figure out how to write a test it in proper way. I use NSubstitute to create mocks.
Class to test:
public interface IMultiThreadClass
{
void Go();
}
public class Lock02 : IMultiThreadClass
{
private readonly IProcessor _processor;
private readonly string _threadName;
private static readonly object Locker = new Object();
public Lock02(IProcessor processor, string threadName)
{
_processor = processor;
_threadName = threadName;
}
public void Go()
{
//critical section
lock (Locker)
{
_processor.Process(_threadName);
}
}
}
Test:
[TestMethod()]
public void Run_Test()
{
//Only one thread should run Processor.Process, but we allow max 2 threads to catch locking erorrs
SemaphoreSlim semaphore = new SemaphoreSlim(1, 2);
//Semaphore to synchronize asserts
SemaphoreSlim synchroSemaphore = new SemaphoreSlim(0, 1);
IProcessor procesor = Substitute.For<IProcessor>();
procesor.When(x => x.Process(Arg.Any<string>())).Do(y =>
{
//increment counter to check if method was called
Interlocked.Increment(ref _counter);
//release synchro semaphore
synchroSemaphore.Release();
//stop thread and wait for release
semaphore.Wait();
});
Lock02 locker1 = new Lock02(procesor, "1");
Lock02 locker2 = new Lock02(procesor, "2");
Lock02 locker3 = new Lock02(procesor, "3");
Task.Run(() => locker1.Go());
Task.Run(() => locker2.Go());
Task.Run(() => locker3.Go());
//ASSERT
//Thread.Sleep(1000);
synchroSemaphore.Wait();
Assert.AreEqual(1, _counter);
semaphore.Release(1);
synchroSemaphore.Wait();
Assert.AreEqual(2, _counter);
semaphore.Release(1);
synchroSemaphore.Wait();
Assert.AreEqual(3, _counter);
semaphore.Release(1);
}
A possible (simple but not bulletproof) way is to spawn some threads/tasks in the unit test, each fetching and temporarily storing an int variable (possibly static), waiting for a bit (delay), incrementing the value and writing it back to the variable. Without thread synchronization (lock), many if not all threads will grab the same number and it will not be equal (as it should) to the number of threads/tasks.
This is not bulletproof since there is still a race condition making it not reproducible (the smelly code is the 50 ms delay), although it seems (to me) very unlikely for all treads to wait for each other in the perfect way and produce the right result.
I consider this being a smelly workaround, but it is simple and works.
[TestMethod]
public async Task APossibleTest()
{
int importantNumber = 0;
var proc = Substitute.For<IProcessor>();
proc.WhenForAnyArgs(processor => processor.Process(Arg.Any<string>()))
.Do(callInfo =>
{
int cached = importantNumber;
// Wait for other threads to fetch the number too (if they were not synchronized).
Thread.Sleep(TimeSpan.FromMilliseconds(50));
// This kind of incrementation will check the thread synchronization.
// Using a thread-safe Interlocked or other here does not make sense.
importantNumber = cached + 1;
});
var locker = new Locker(proc, "da horror");
// Create 10 tasks all attempting to increment the important number.
Task[] tasks =
Enumerable
.Range(0, 10)
// You could create multiple lockers here (with their own processors).
.Select(i => Task.Run(() => locker.Go()))
.ToArray();
await Task.WhenAll(tasks);
Assert.AreEqual(10, importantNumber, "Exactly 10 increments were expected since we have 10 tasks.");
}

Thread synchronization in the Task Parallel Library (TPL)

I am learning TPL and stuck with a doubt. It is only for learning purpose and I hope people will guide me in the correct direction.
I want only one thread to access the variable sum at one time so that it does not get overwritten.
The code I have is below.
using System;
using System.Threading.Tasks;
class ThreadTest
{
private Object thisLock = new Object();
static int sum = 0;
public void RunMe()
{
lock (thisLock)
{
sum = sum + 1;
}
}
static void Main()
{
ThreadTest b = new ThreadTest();
Task t1 = new Task(()=>b.RunMe());
Task t2= new Task(() => b.RunMe());
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(sum.ToString());
Console.ReadLine();
}
}
Question -Am i right in this code ?
Question-Can I do it without lock because I read it somewhere that it should be avoided as it does not allow task to communicate with each other.I have seen some examples with async and await but I am using .Net 4.0 .
Thanks
Am i right in this code
Implementation wise Yes, but understanding wise No, as you have mixed up the new and old world while trying to implement the logic, let me try to explain in detail.
Task t1 = new Task(()=>b.RunMe()); doesn't mean as expected as in case of Thread API a new thread every time
Task API will invoke a thread pool thread, so chances are two Task objects t1,t2, gets executed on same thread most of the times for a short running logic and there's never a race condition, which needs an explicit lock, while trying to update the shared object
Better way to prevent race condition for Sum object would be Interlocked.Increment(ref sum), which is a thread safe mechanism to do basic operations on primitive types
For the kind of operation you are doing a better API would be Parallel.For, instead of creating a separate Task, the benefit would be you can run any number of such increment operations with minimal effort, instead of creating a Separate Task and it automatically blocks the Main thread, so your code shall look like:
using System;
using System.Threading.Tasks;
class ThreadTest
{
public static int sum;
}
static void Main()
{
Parallel.For(0, 1, i =>
{
// Some thread instrumentation
Console.WriteLine("i = {0}, thread = {1}", i,
Thread.CurrentThread.ManagedThreadId);
Interlocked.Increment(ref ThreadTest.sum);
});
Console.WriteLine(ThreadTest.sum.ToString());
Console.ReadLine();
}
}
While using the Thread instrumentation you will find that chances are that for two loops, 0,1, managed thread id is same, thus obviating the need for thread safety as suggested earlier
Answer 1:
This is threadsafe for the code that you posted.
However, as Sam has pointed out, this is not currently threadsafe in the general case because the field being incremented is static, but the locking object is not static.
This means that two separate instances of ThreadTest could be created on two separate threads, and then RunMe() could be called from those threads and because each instance has a separate locking object, the locking wouldn't work.
The solution here is to make the locking object static too.
Answer 2:
You can do this without explicit locking using Interlocked.Increment():
public void RunMe()
{
Interlocked.Increment(ref sum);
}
Now to the point, as I was adding some unhappy comments about downvotes, that have no reasons:).
Your scenario is working and is classic multithreading usage.
Classic because of using system lock, that is lock are actually WinAPI locks of the OS, so in order to synchronize, the code has to manage to ring down to the OS and back and of course lose some time with switching threads as some contention may happen especially if you would access RunMe multiple times in each thread running given task or created even more concurrent tasks thank 2.
Please try look on atomic operations.
For your scenario it would work very well, Interlocked.Increment(ref sum).
From there you have to restrain yourself from directly accessing the sum, but that is not a problem, because the Increment method is returning latest result.
Another option is to use SpinLock, that is IF YOU OPERATION IS REALLY FAST.
NEVER ON something ,like Console.WriteLine or any other system operations or long running calculations etc.
Here Inrelocked examples:
using System;
using System.Threading;
using System.Threading.Tasks;
class ThreadTest
{
/// <summary> DO NOT TOUCH ME DIRECTLY </summary>
private static int sum/* = 0 zero is default*/;
private static int Add(int add) => Interlocked.Add(ref sum, add);
private static int Increment() => Interlocked.Increment(ref sum);
private static int Latest() => Interlocked.Add(ref sum, 0);
private static void RunMe() => Increment();
static void Main()
{
Task t1 = new Task(RunMe);
Task t2 = new Task(RunMe);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(Latest().ToString());
Console.ReadLine();
}
}

Use tasks and ContinueWith to implement scheduler

I'm trying to write my own scheduler; the rationale behind it is that all the submitted actions will be executed in order, according to a delay. For example, if at time 0 I schedule action A with delay 5 and at time 1 I schedule action B with delay 2, then B should be executed first at time 3 and A should be executed second, at time 5.
Basically, what I am trying to do is something like:
public class MyScheduler
{
Task _task = new Task(() => { });
public MyScheduler()
{
_task.Start();
}
public void Schedule(Action action, long delay)
{
Task.Delay(TimeSpan.FromTicks(delay)).ContinueWith(_ =>
lock(_task) {
_task = _task.ContinueWith(task => action())
}
);
}
}
A relevant test for this code would be:
var waiter = new Waiter(3);
int _count = 0;
mysched = new MyScheduler();
mysched.Schedule(() => { _count++; waiter.Signal(); });
mysched.Schedule(() => { Task.Delay(100).Wait(); _count *= 3; waiter.Signal(); });
mysched.Schedule(() => { _count++; waiter.Signal(); });
waiter.Await();
Assert.AreEqual(4, _count);
In the above code, Waiter is a class with an internal variable initialized in the constructor; the Signal method decrements that internal variable and the Await method loops (and sleeps 10 ms on each iteration) until the internal variable is less than or equal to zero.
The aim of the test is to show that the scheduled actions have been performed in order.
Most of the times this is true and the test passes, but on few occasions the resulting value for _count is 2 instead of 4. I have spent a lot of time trying to figure out why this happens, but I can't seem to figure it out and my lack of experience with C# is not helping either.
Does anyone have any suggestions?
For one thing, _count is not synchronized for access from different threads.
I recommend that you not use ContinueWith at all; it is a very low-level method and is very easy to get the details wrong (for example, the default scheduler is TaskScheduler.Current, which is almost never what you want). Your general logic code should use await instead of ContinueWith.
Regarding the scheduler, these days it is almost impossible to make a good use case for developing your own. There are better ones available that are developed by geniuses and extremely well-tested. Consider Reactive Extensions: they provide several schedulers, and they all support scheduling.

How do Tasks in the Task Parallel Library affect ActivityID?

Before using the Task Parallel Library, I have often used CorrelationManager.ActivityId to keep track of tracing/error reporting with multiple threads.
ActivityId is stored in Thread Local Storage, so each thread get's its own copy. The idea is that when you fire up a thread (activity), you assign a new ActivityId. The ActivityId will be written to the logs with any other trace information, making it possible to single out the trace information for a single 'Activity'. This is really useful with WCF as the ActivityId can be carried over to the service component.
Here is an example of what I'm talking about:
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
{
DoWork();
}));
}
static void DoWork()
{
try
{
Trace.CorrelationManager.ActivityId = Guid.NewGuid();
//The functions below contain tracing which logs the ActivityID.
CallFunction1();
CallFunction2();
CallFunction3();
}
catch (Exception ex)
{
Trace.Write(Trace.CorrelationManager.ActivityId + " " + ex.ToString());
}
}
Now, with the TPL, my understanding is that multiple Tasks share Threads. Does this mean that ActivityId is prone to being reinitialized mid-task (by another task)? Is there a new mechanism to deal with activity tracing?
I ran some experiments and it turns out the assumption in my question is incorrect - multiple tasks created with the TPL do not run on the same thread at the same time.
ThreadLocalStorage is safe to use with TPL in .NET 4.0, since a thread can only be used by one task at a time.
The assumption that tasks can share threads concurrently was based on an interview I heard about c# 5.0 on DotNetRocks (sorry, I can't remember which show it was) - so my question may (or may not) become relevant soon.
My experiment starts a number of tasks, and records how many tasks ran, how long they took, and how many threads were consumed. The code is below if anyone would like to repeat it.
class Program
{
static void Main(string[] args)
{
int totalThreads = 100;
TaskCreationOptions taskCreationOpt = TaskCreationOptions.None;
Task task = null;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Task[] allTasks = new Task[totalThreads];
for (int i = 0; i < totalThreads; i++)
{
task = Task.Factory.StartNew(() =>
{
DoLongRunningWork();
}, taskCreationOpt);
allTasks[i] = task;
}
Task.WaitAll(allTasks);
stopwatch.Stop();
Console.WriteLine(String.Format("Completed {0} tasks in {1} milliseconds", totalThreads, stopwatch.ElapsedMilliseconds));
Console.WriteLine(String.Format("Used {0} threads", threadIds.Count));
Console.ReadKey();
}
private static List<int> threadIds = new List<int>();
private static object locker = new object();
private static void DoLongRunningWork()
{
lock (locker)
{
//Keep a record of the managed thread used.
if (!threadIds.Contains(Thread.CurrentThread.ManagedThreadId))
threadIds.Add(Thread.CurrentThread.ManagedThreadId);
}
Guid g1 = Guid.NewGuid();
Trace.CorrelationManager.ActivityId = g1;
Thread.Sleep(3000);
Guid g2 = Trace.CorrelationManager.ActivityId;
Debug.Assert(g1.Equals(g2));
}
}
The output (of course this will depend on the machine) was:
Completed 100 tasks in 23097 milliseconds
Used 23 threads
Changing taskCreationOpt to TaskCreationOptions.LongRunning gave different results:
Completed 100 tasks in 3458 milliseconds
Used 100 threads
Please forgive my posting this as an answer as it is not really answer to your question, however, it is related to your question since it deals with CorrelationManager behavior and threads/tasks/etc. I have been looking at using the CorrelationManager's LogicalOperationStack (and StartLogicalOperation/StopLogicalOperation methods) to provide additional context in multithreading scenarios.
I took your example and modified it slightly to add the ability to perform work in parallel using Parallel.For. Also, I use StartLogicalOperation/StopLogicalOperation to bracket (internally) DoLongRunningWork. Conceptually, DoLongRunningWork does something like this each time it is executed:
DoLongRunningWork
StartLogicalOperation
Thread.Sleep(3000)
StopLogicalOperation
I have found that if I add these logical operations to your code (more or less as is), all of the logical operatins remain in sync (always the expected number of operations on stack and the values of the operations on the stack are always as expected).
In some of my own testing I found that this was not always the case. The logical operation stack was getting "corrupted". The best explanation I could come up with is that the "merging" back of the CallContext information into the "parent" thread context when the "child" thread exits was causing the "old" child thread context information (logical operation) to be "inherited" by another sibling child thread.
The problem might also be related to the fact that Parallel.For apparently uses the main thread (at least in the example code, as written) as one of the "worker threads" (or whatever they should be called in the parallel domain). Whenever DoLongRunningWork is executed, a new logical operation is started (at the beginning) and stopped (at the end) (that is, pushed onto the LogicalOperationStack and popped back off of it). If the main thread already has a logical operation in effect and if DoLongRunningWork executes ON THE MAIN THREAD, then a new logical operation is started so the main thread's LogicalOperationStack now has TWO operations. Any subsequent executions of DoLongRunningWork (as long as this "iteration" of DoLongRunningWork is executing on the main thread) will (apparently) inherit the main thread's LogicalOperationStack (which now has two operations on it, rather than just the one expected operation).
It took me a long time to figure out why the behavior of the LogicalOperationStack was different in my example than in my modified version of your example. Finally I saw that in my code I had bracketed the entire program in a logical operation, whereas in my modified version of your test program I did not. The implication is that in my test program, each time my "work" was performed (analogous to DoLongRunningWork), there was already a logical operation in effect. In my modified version of your test program, I had not bracketed the entire program in a logical operation.
So, when I modified your test program to bracket the entire program in a logical operation AND if I am using Parallel.For, I ran into exactly the same problem.
Using the conceptual model above, this will run successfully:
Parallel.For
DoLongRunningWork
StartLogicalOperation
Sleep(3000)
StopLogicalOperation
While this will eventually assert due to an apparently out of sync LogicalOperationStack:
StartLogicalOperation
Parallel.For
DoLongRunningWork
StartLogicalOperation
Sleep(3000)
StopLogicalOperation
StopLogicalOperation
Here is my sample program. It is similar to yours in that it has a DoLongRunningWork method that manipulates the ActivityId as well as the LogicalOperationStack. I also have two flavors of kicking of DoLongRunningWork. One flavor uses Tasks one uses Parallel.For. Each flavor can also be executed such that the whole parallelized operation is enclosed in a logical operation or not. So, there are a total of 4 ways to execute the parallel operation. To try each one, simply uncomment the desired "Use..." method, recompile, and run. UseTasks, UseTasks(true), and UseParallelFor should all run to completion. UseParallelFor(true) will assert at some point because the LogicalOperationStack does not have the expected number of entries.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace CorrelationManagerParallelTest
{
class Program
{
static void Main(string[] args)
{
//UseParallelFor(true) will assert because LogicalOperationStack will not have expected
//number of entries, all others will run to completion.
UseTasks(); //Equivalent to original test program with only the parallelized
//operation bracketed in logical operation.
////UseTasks(true); //Bracket entire UseTasks method in logical operation
////UseParallelFor(); //Equivalent to original test program, but use Parallel.For
//rather than Tasks. Bracket only the parallelized
//operation in logical operation.
////UseParallelFor(true); //Bracket entire UseParallelFor method in logical operation
}
private static List<int> threadIds = new List<int>();
private static object locker = new object();
private static int mainThreadId = Thread.CurrentThread.ManagedThreadId;
private static int mainThreadUsedInDelegate = 0;
// baseCount is the expected number of entries in the LogicalOperationStack
// at the time that DoLongRunningWork starts. If the entire operation is bracketed
// externally by Start/StopLogicalOperation, then baseCount will be 1. Otherwise,
// it will be 0.
private static void DoLongRunningWork(int baseCount)
{
lock (locker)
{
//Keep a record of the managed thread used.
if (!threadIds.Contains(Thread.CurrentThread.ManagedThreadId))
threadIds.Add(Thread.CurrentThread.ManagedThreadId);
if (Thread.CurrentThread.ManagedThreadId == mainThreadId)
{
mainThreadUsedInDelegate++;
}
}
Guid lo1 = Guid.NewGuid();
Trace.CorrelationManager.StartLogicalOperation(lo1);
Guid g1 = Guid.NewGuid();
Trace.CorrelationManager.ActivityId = g1;
Thread.Sleep(3000);
Guid g2 = Trace.CorrelationManager.ActivityId;
Debug.Assert(g1.Equals(g2));
//This assert, LogicalOperation.Count, will eventually fail if there is a logical operation
//in effect when the Parallel.For operation was started.
Debug.Assert(Trace.CorrelationManager.LogicalOperationStack.Count == baseCount + 1, string.Format("MainThread = {0}, Thread = {1}, Count = {2}, ExpectedCount = {3}", mainThreadId, Thread.CurrentThread.ManagedThreadId, Trace.CorrelationManager.LogicalOperationStack.Count, baseCount + 1));
Debug.Assert(Trace.CorrelationManager.LogicalOperationStack.Peek().Equals(lo1), string.Format("MainThread = {0}, Thread = {1}, Count = {2}, ExpectedCount = {3}", mainThreadId, Thread.CurrentThread.ManagedThreadId, Trace.CorrelationManager.LogicalOperationStack.Peek(), lo1));
Trace.CorrelationManager.StopLogicalOperation();
}
private static void UseTasks(bool encloseInLogicalOperation = false)
{
int totalThreads = 100;
TaskCreationOptions taskCreationOpt = TaskCreationOptions.None;
Task task = null;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StartLogicalOperation();
}
Task[] allTasks = new Task[totalThreads];
for (int i = 0; i < totalThreads; i++)
{
task = Task.Factory.StartNew(() =>
{
DoLongRunningWork(encloseInLogicalOperation ? 1 : 0);
}, taskCreationOpt);
allTasks[i] = task;
}
Task.WaitAll(allTasks);
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StopLogicalOperation();
}
stopwatch.Stop();
Console.WriteLine(String.Format("Completed {0} tasks in {1} milliseconds", totalThreads, stopwatch.ElapsedMilliseconds));
Console.WriteLine(String.Format("Used {0} threads", threadIds.Count));
Console.WriteLine(String.Format("Main thread used in delegate {0} times", mainThreadUsedInDelegate));
Console.ReadKey();
}
private static void UseParallelFor(bool encloseInLogicalOperation = false)
{
int totalThreads = 100;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StartLogicalOperation();
}
Parallel.For(0, totalThreads, i =>
{
DoLongRunningWork(encloseInLogicalOperation ? 1 : 0);
});
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StopLogicalOperation();
}
stopwatch.Stop();
Console.WriteLine(String.Format("Completed {0} tasks in {1} milliseconds", totalThreads, stopwatch.ElapsedMilliseconds));
Console.WriteLine(String.Format("Used {0} threads", threadIds.Count));
Console.WriteLine(String.Format("Main thread used in delegate {0} times", mainThreadUsedInDelegate));
Console.ReadKey();
}
}
}
This whole issue of if LogicalOperationStack can be used with Parallel.For (and/or other threading/Task constructs) or how it can be used probably merits its own question. Maybe I will post a question. In the meantime, I wonder if you have any thoughts on this (or, I wonder if you had considered using LogicalOperationStack since ActivityId appears to be safe).
[EDIT]
See my answer to this question for more information about using LogicalOperationStack and/or CallContext.LogicalSetData with some of the various Thread/ThreadPool/Task/Parallel contstructs.
See also my question here on SO about LogicalOperationStack and Parallel extensions:
Is CorrelationManager.LogicalOperationStack compatible with Parallel.For, Tasks, Threads, etc
Finally, see also my question here on Microsoft's Parallel Extensions forum:
http://social.msdn.microsoft.com/Forums/en-US/parallelextensions/thread/7c5c3051-133b-4814-9db0-fc0039b4f9d9
In my testing it looks like Trace.CorrelationManager.LogicalOperationStack can become corrupted when using Parallel.For or Parallel.Invoke IF you start a logical operation in the main thread and then start/stop logical operations in the delegate. In my tests (see either of the two links above) the LogicalOperationStack should always have exactly 2 entries when DoLongRunningWork is executing (if I start a logical operation in the main thread before kicking of DoLongRunningWork using various techniques). So, by "corrupted" I mean that the LogicalOperationStack will eventually have many more than 2 entries.
From what I can tell, this is probably because Parallel.For and Parallel.Invoke use the main thread as one of the "worker" threads to perform the DoLongRunningWork action.
Using a stack stored in CallContext.LogicalSetData to mimic the behavior of the LogicalOperationStack (similar to log4net's LogicalThreadContext.Stacks which is stored via CallContext.SetData) yields even worse results. If I am using such a stack to maintain context, it becomes corrupted (i.e. does not have the expected number of entries) in almost all of the scenarios where I have a "logical operation" in the main thread and a logical operation in each iteration/execution of the DoLongRunningWork delegate.

Categories