Why does Rhino Mocks throws an Exception when using it with threads? - c#

we have a weired problem when using Rhino Mocks and Threads. I've
tried to isolate the problem, but now I'm stuck to this:
[TestClass]
public class FoolTests
{
[TestMethod]
public void TestMethod_Scenario_Result()
{
for (int i = 0; i < 5; i++)
{
var fool = MockRepository.GenerateStub<IFool>();
fool.Stub(x => x.AmIFool).Return(false);
new Fool(fool);
}
}
}
public class Fool
{
private readonly IFool _fool;
private readonly Thread _thread;
public Fool(IFool fool)
{
_fool = fool;
_thread = new Thread(Foolish);
_thread.Start();
}
private void Foolish()
{
while (true)
{
var foo = _fool.Foolness;
}
}
}
public interface IFool
{
bool AmIFool { get; }
bool Foolness { get; set; }
}
Nearly all the time when running this test, I get "Test method FoolTests.TestMethod_Scenario_Result threw exception: System.InvalidOperationException: This action is invalid when the mock object is in replay state." on line "fool.Stub(x => x.AmIFool).Return(false);".
I have no idea what should be wrong here. Has anyone an idea or do I have to dig into the Rhino Mocks-code?

Not sure if this is a complete answer, but this page has an interesting note on multi-threading:
http://www.ayende.com/projects/rhino-mocks/api/files/MockRepository-cs.html
MockRepository is capable of verifying in multiply threads, but recording in multiply threads is not recommended.
First thing I'd try is setting up all mocks and expectations, then executing your constructors. This seems to be working for me:
[TestMethod]
public void TestMethod_Scenario_Result()
{
var stubs = new IFool[5];
for (int i = 0; i < stubs.Length; ++i)
{
var fool = MockRepository.GenerateStub<IFool>();
fool.Stub(x => x.AmIFool).Return(false);
stubs[i] = fool;
}
foreach (var stub in stubs)
new Fool(stub);
}
Since this code works, I imagine the problem is that you are doing playback in one thread while recording (for a different stub) in another thread. Playing back and recording at the same time seems like it isn't thread safe, even if you're operating on different stubs.

Related

C# Trying to wrap a function with a stopwatch

I've been attempting to see how long functions take to execute in my code as practice to see where I can optimize. Right now I use a helper class that is essentially a stopwatch with a message to check these. The goal of this is that I should be able to wrap whatever method call I want in the helper and I'll get it's duration.
public class StopwatcherData
{
public long Time { get; set; }
public string Message { get; set; }
public StopwatcherData(long time, string message)
{
Time = time;
Message = message;
}
}
public class Stopwatcher
{
public delegate void CompletedCallBack(string result);
public static List<StopwatcherData> Data { get; set; }
private static Stopwatch stopwatch { get; set;}
public Stopwatcher()
{
Data = new List<StopwatcherData>();
stopwatch = new Stopwatch();
stopwatch.Start();
}
public static void Click(string message)
{
Data.Add(new StopwatcherData(stopwatch.ElapsedMilliseconds, message));
}
public static void Reset()
{
stopwatch.Reset();
stopwatch.Start();
}
}
Right now to use this, I have to call the Reset before the function I want so that the timer is restarted, and then call the click after it.
Stopwatcher.Reset()
MyFunction();
Stopwatcher.Click("MyFunction");
I've read a bit about delegates and actions, but I'm unsure of how to apply them to this situation. Ideally, I would pass the function as part of the Stopwatcher call.
//End Goal:
Stopwatcher.Track(MyFunction(), "MyFunction Time");
Any help is welcome.
It's not really a good idea to profile your application like that, but if you insist, you can at least make some improvements.
First, don't reuse Stopwatch, just create new every time you need.
Second, you need to handle two cases - one when delegate you pass returns value and one when it does not.
Since your Track method is static - it's a common practice to make it thread safe. Non-thread-safe static methods are quite bad idea. For that you can store your messages in a thread-safe collection like ConcurrentBag, or just use lock every time you add item to your list.
In the end you can have something like this:
public class Stopwatcher {
private static readonly ConcurrentBag<StopwatcherData> _data = new ConcurrentBag<StopwatcherData>();
public static void Track(Action action, string message) {
var w = Stopwatch.StartNew();
try {
action();
}
finally {
w.Stop();
_data.Add(new StopwatcherData(w.ElapsedMilliseconds, message));
}
}
public static T Track<T>(Func<T> func, string message) {
var w = Stopwatch.StartNew();
try {
return func();
}
finally {
w.Stop();
_data.Add(new StopwatcherData(w.ElapsedMilliseconds, message));
}
}
}
And use it like this:
Stopwatcher.Track(() => SomeAction(param1), "test");
bool result = Stopwatcher.Track(() => SomeFunc(param2), "test");
If you are going to use that with async delegates (which return Task or Task<T>) - you need to add two more overloads for that case.
Yes, you can create a timer function that accepts any action as a delegate. Try this block:
public static long TimeAction(Action action)
{
var timer = new Stopwatch();
timer.Start();
action();
timer.Stop();
return timer.ElapsedMilliseconds;
}
This can be used like this:
var elapsedMilliseconds = TimeAction(() => MyFunc(param1, param2));
This is a bit more awkward if your wrapped function returns a value, but you can deal with this by assigning a variable from within the closure, like this:
bool isSuccess ;
var elapsedMilliseconds = TimeToAction(() => {
isSuccess = MyFunc(param1, param2);
});
I've had this problem a while ago as well and was always afraid of the case that I'll leave errors when I change Stopwatcher.Track(() => SomeFunc(), "test")(See Evk's answer) back to SomeFunc(). So I tought about something that wraps it without changing it!
I came up with a using, which is for sure not the intended purpose.
public class OneTimeStopwatch : IDisposable
{
private string _logPath = "C:\\Temp\\OneTimeStopwatch.log";
private readonly string _itemname;
private System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
public OneTimeStopwatch(string itemname)
{
_itemname = itemname;
sw.Start();
}
public void Dispose()
{
sw.Stop();
System.IO.File.AppendAllText(_logPath, string.Format($"{_itemname}: {sw.ElapsedMilliseconds}ms{Environment.NewLine}"));
}
}
This can be used a easy way
using (new OneTimeStopwatch("test"))
{
//some sensible code not to touch
System.Threading.Thread.Sleep(1000);
}
//logfile with line "test: 1000ms"
I only need to remove 2 lines (and auto format) to make it normal again.
Plus I can easily wrap multiple lines here which isn't possible without defining new functions in the other approach.
Again, this is not recommended for terms of few miliseconds.

How to troubleshoot the situation where XUnit tests fail with assertion of messages in NLog target under "Run All"

This is my environment:
Visual Studio 2017
Project's .NET runtime version is 4.6.2
XUnit version 2.3.1
NLog version 4.4.12
Fluent Assertions 4.19.4
This is the problem:
When I run the tests individually they pass, however when i run time via the "Run All" button in Test Explorer, I get failures and when repeating the running of the subsequent failed tasks, all of them pass eventually. Also want to point out that I am not running the tests in parallel. The nature of the tests are such that the code under test emits log information which eventually ends up in a custom NLog Target. Here is an example program which can be run in order to reproduce the problem.
using FluentAssertions;
using NLog;
using NLog.Common;
using NLog.Config;
using NLog.Targets;
using System;
using System.Collections.Concurrent;
using System.IO;
using Xunit;
namespace LoggingTests
{
[Target("test-target")]
public class TestTarget : TargetWithLayout
{
public ConcurrentBag<string> Messages = new ConcurrentBag<string>();
public TestTarget(string name)
{
Name = name;
}
protected override void Write(LogEventInfo logEvent)
{
Messages.Add(Layout.Render(logEvent));
}
}
class Loggable
{
private Logger _logger;
public Loggable()
{
_logger = LogManager.GetCurrentClassLogger();
}
private void Log(LogLevel level,
Exception exception,
string message,
params object[] parameters)
{
LogEventInfo log_event = new LogEventInfo();
log_event.Level = level;
log_event.Exception = exception;
log_event.Message = message;
log_event.Parameters = parameters;
log_event.LoggerName = _logger.Name;
_logger.Log(log_event);
}
public void Debug(string message)
{
Log(LogLevel.Debug,
null,
message,
null);
}
public void Error(string message)
{
Log(LogLevel.Error,
null,
message,
null);
}
public void Info(string message)
{
Log(LogLevel.Info,
null,
message,
null);
}
public void Fatal(string message)
{
Log(LogLevel.Fatal,
null,
message,
null);
}
}
public class Printer
{
public delegate void Print(string message);
private Print _print_function;
public Printer(Print print_function)
{
_print_function = print_function;
}
public void Run(string message_template,
int number_of_times)
{
for (int i = 0; i < number_of_times; i++)
{
_print_function($"{message_template} - {i}");
}
}
}
public abstract class BaseTest
{
protected string _target_name;
public BaseTest(LogLevel log_level)
{
if (LogManager.Configuration == null)
{
LogManager.Configuration = new LoggingConfiguration();
InternalLogger.LogLevel = LogLevel.Debug;
InternalLogger.LogFile = Path.Combine(Environment.CurrentDirectory,
"nlog_debug.txt");
}
// Register target:
_target_name = GetType().Name;
Target.Register<TestTarget>(_target_name);
// Create Target:
TestTarget t = new TestTarget(_target_name);
t.Layout = "${message}";
// Add Target to configuration:
LogManager.Configuration.AddTarget(_target_name,
t);
// Add a logging rule pertaining to the above target:
LogManager.Configuration.AddRule(log_level,
log_level,
t);
// Because configuration has been modified programatically, we have to reconfigure all loggers:
LogManager.ReconfigExistingLoggers();
}
protected void AssertTargetContains(string message)
{
TestTarget target = (TestTarget)LogManager.Configuration.FindTargetByName(_target_name);
target.Messages.Should().Contain(message);
}
}
public class TestA : BaseTest
{
public TestA() : base(LogLevel.Info)
{
}
[Fact]
public void SomeTest()
{
int number_of_times = 100;
(new Printer((new Loggable()).Info)).Run(GetType().Name,
number_of_times);
for (int i = 0; i < number_of_times; i++)
{
AssertTargetContains($"{GetType().Name} - {i}");
}
}
}
public class TestB : BaseTest
{
public TestB() : base(LogLevel.Debug)
{
}
[Fact]
public void SomeTest()
{
int number_of_times = 100;
(new Printer((new Loggable()).Debug)).Run(GetType().Name,
number_of_times);
for (int i = 0; i < number_of_times; i++)
{
AssertTargetContains($"{GetType().Name} - {i}");
}
}
}
public class TestC : BaseTest
{
public TestC() : base(LogLevel.Error)
{
}
[Fact]
public void SomeTest()
{
int number_of_times = 100;
(new Printer((new Loggable()).Error)).Run(GetType().Name,
number_of_times);
for (int i = 0; i < number_of_times; i++)
{
AssertTargetContains($"{GetType().Name} - {i}");
}
}
}
public class TestD : BaseTest
{
public TestD() : base(LogLevel.Fatal)
{
}
[Fact]
public void SomeTest()
{
int number_of_times = 100;
(new Printer((new Loggable()).Fatal)).Run(GetType().Name,
number_of_times);
for (int i = 0; i < number_of_times; i++)
{
AssertTargetContains($"{GetType().Name} - {i}");
}
}
}
}
The above test code runs better. After some earlier troubleshooting by following the messages, it appeared that I was not calling LogManager.ReconfigExistingLoggers(); since the configurations are being created programatically (in the constructors of the test classes). Here is a note in the source code of LogManager:
/// Loops through all loggers previously returned by GetLogger.
/// and recalculates their target and filter list. Useful after modifying the configuration programmatically
/// to ensure that all loggers have been properly configured.
Afterwards all the tests ran as expected with occasional failures like shown below:
I am wondering now if there is anything more I should be securing in my test setup or is this rather a bug NLog. Any suggestion on how to go about to fix my test setup or, troubleshoot the setup would be most welcome. Thanks in advance.
Update
Changed List<LogData> to ConcurrentBag<LogData>. This however does not change the problem. Problem remains that messages are not arriving into collection in a timely manner.
Reformulated the problem and replaced previous code sample with an actual example (that can be run to reproduce the problem) + screenshots of the problem.
Improved tests which run better but occasionally fail due to exceptions coming from NLog itself (added screenshot).
The problem with above issue happens to be with XUnit Runner in VisualStudio. Eventhough i had 'DO NOT RUN TESTS IN PARALLEL' disabled, the tests were running in parallel somehow. #rolf-kristensen pointed in another NLog issue (Ref: https://github.com/NLog/NLog/issues/2525) that he had added the following:
[assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)]
in the AssemblyInfo.cs file. This configuration is also mentioned on XUnit's page (Ref: https://xunit.github.io/docs/running-tests-in-parallel.html - Changing Default Behavior)
Very random code you have shown, and very random details about what is failing. So maybe my suggestions doesn't make sense to your problem.
Instead of calling TestLogTarget directly, then you should setup a logging configuration:
var target = new TestLogTarget() { Name = "Test" };
NLog.Config.SimpleConfigurator(target);
var logger = NLog.LogManager.GetCurrentClassLogger();
logger.Info("Hello World");
Make sure to add a lock around the access to the Messages. Either by making a ToArray() while holding the lock (or calling Contains while holding the lock)
Remember that NLog is a global engine, which require special effort in a unit testing environment where test-classes, test-appdomains are stopped and started frequently, so you need to know your unit-test-system and your nlog-system to make them work together.

Verifying progress reported from an async call is properly report in unit tests

I'm working on some code that can automatically detect the serial port that a device (in this case, a spectrometer) is connected to.
I have the auto detection piece working, and I'm trying to write tests for the ViewModel that shows the progress of detection, errors, etc.
Here's the interface to the code that does the actual detection.
public interface IAutoDetector
{
Task DetectSpectrometerAsync(IProgress<int> progress);
bool IsConnected { get; set; }
string SelectedSerialPort { get; set; }
}
Here is the ViewModel that uses the IAutoDetector to detect the spectrometer
public class AutoDetectViewModel : Screen
{
private IAutoDetector autoDetect;
private int autoDetectionProgress;
public int AutoDetectionProgress
{
get { return autoDetectionProgress; }
private set
{
autoDetectionProgress = value;
NotifyOfPropertyChange();
}
}
[ImportingConstructor]
public AutoDetectViewModel(IAutoDetector autoDetect)
{
this.autoDetect = autoDetect;
}
public async Task AutoDetectSpectrometer()
{
Progress<int> progressReporter = new Progress<int>(ProgressReported);
await autoDetect.DetectSpectrometerAsync(progressReporter);
}
private void ProgressReported(int progress)
{
AutoDetectionProgress = progress;
}
}
I'm trying to write a test that verifies progress reported from the IAutoDetector updates the AutoDetectionProgress property in the AutoDetectionViewModel.
Here's my current (non-working) test:
[TestMethod]
public async Task DetectingSpectrometerUpdatesTheProgress()
{
Mock<IAutoDetector> autoDetectMock = new Mock<IAutoDetector>();
AutoDetectViewModel viewModel = new AutoDetectViewModel(autoDetectMock.Object);
IProgress<int> progressReporter = null;
autoDetectMock.Setup(s => s.DetectSpectrometerAsync(It.IsAny<IProgress<int>>()))
.Callback((prog) => { progressReporter = prog; });
await viewModel.AutoDetectSpectrometer();
progressReporter.Report(10);
Assert.AreEqual(10, viewModel.AutoDetectionProgress);
}
What I want to do is grab the IProgress<T> that is passed to autoDetect.DetectSpectrometerAsync(progressReporter), tell the IProgress<T> to report a progress of 10, then make sure the AutoDetectionProgress in the viewModel is 10 as well.
However, there's 2 problems with this code:
It does not compile. The autoDetectMock.Setup line has an error: Error 1 Delegate 'System.Action' does not take 1 arguments. I've used the same technique in other (non-synchronous) tests to gain access to passed values.
Will this approach even work? If my understanding of async is correct, the call await viewModel.AutoDetectSpectrometer(); will wait for the call to finish before calling progressReporter.Report(10);, which won't have any effect because the AutoDetectSpectrometer() call has returned already.
You must specify the return type for the callback; the compiler will not be able to determine this for you.
autoDetectMock
.Setup(s => s.DetectSpectrometerAsync(It.IsAny<IProgress<int>>()))
.Callback((prog) => { progressReporter = prog; }); // what you have
should be
autoDetectMock
.Setup(s => s.DetectSpectrometerAsync(It.IsAny<IProgress<int>>()))
.Callback<IProgress<int>>((prog) => { progressReporter = prog; });
You also aren't returning a Task from your Setup, so that will fail as well. You'd need to return a Task.
I believe, after you fix these two things, it should work.

Testing IObservable 'dispatcher'

I have several different 'types' of incoming events, and I want to dispatch them to different IObservables, exposed as properties, but without subscribing multiple times to the underlying UDP.
public IObservable<TimeEvent> TimeEventChannel { get; private set; }
public IObservable<SpaceEvent> SpaceEventChannel { get; private set; }
Subject<TimeEvent> _TimeSubject = new Subject<TimeEvent>();
Subject<SpaceEvent> _SpaceSubject = new Subject<SpaceEvent>();
public EventDispatcher(IChannelListener listener)
{
TimeEventChannel = _TimeSubject;
SpaceEventChannel = _SpaceSubject;
listener.Data.Subscribe(SwitchEvent);
}
private void SwitchEvent(AbstractEvent e)
{
switch(e.EventType)
{
case EEventType.Time: _TimeSubject.OnNext(e as TimeEvent); break;
case EEventType.Space: _SpaceSubject.OnNext(e as SpaceEvent); break;
}
}
(listener.Data is an IObservable<AbstractEvent>).
The problem I'm having is trying to work out how to test this in isolation (without hooking up to UDP)
var spaceEvent = new SpaceEvent();
var udpSubject = new Subject<AbstractEvent>();
var mock = new Mock<IChannelListener>();
mock.SetupGet(listener => listener.Data).Returns(udpSubject);
var dispatcher = new EventDispatcher(mock.Object);
subject.OnNext(spaceEvent);
var result = dispatcher.SpaceEventChannel.SingleOrDefault();
As it stands, the test blocks on the last line, and I'm pretty sure it's because there's something I've fundamentally not grokked about how Subject works.
Question: What am I thinking wrong? How should I go about testing this particular use case? Am I implementing the Dispatcher backwards too?
Just in case, this is what the real ChannelListener currently looks like:
public ChannelListener(UdpClient udpClient, FrameInterpreter frameInterpreter)
{
Data = Observable.Defer(() =>
{
IPEndPoint ep = null;
return Observable.FromAsyncPattern<byte[]>(
udpClient.BeginReceive,
i => udpClient.EndReceive(i, ref ep)
)()
.Select(bytes => frameInterpreter.ParseFrame(bytes));
});
}
public IObservable<AbstractEvent> Data { get; private set; }
I think the problem is in the lines:
subject.OnNext(spaceEvent);
var result = dispatcher.SpaceEventChannel.SingleOrDefault();
Try replacing it with:
AbstractEvent result = null;
dispatcher.SpaceEventChannels.Subscribe(e => result = e);
subject.OnNext(spaceEvent);
// ...
The problem is that when you call subject.OnNext, it runs thru the "pipeline" * immediately*. Therefore, the next line's SingleOrDefault actually locks the text, because no value ever "arrives" to it.
The primary issue you have is simple. This line:
listener.Data.Subscribe(SwitchEvent);
Returns an IDisposable. Which immediately goes out of scope and is disposed. So SwitchEvent never fires. You just need to hold that IDisposable in an instance variable in the EventDispatcher class.
private IDisposable _subscription;
public EventDispatcher(IChannelListener listener)
{
TimeEventChannel = _TimeSubject;
SpaceEventChannel = _SpaceSubject;
_subscription = listener.Data.Subscribe(SwitchEvent);
}
I would also seriously consider changing EventDispatcher to accept an IObservable<AbstractEvent> rather than IChannelListener if that's all it really needs. You can imagine how much easier this would be to test too!

Async result handle to return to callers

I have a method that queues some work to be executed asynchronously. I'd like to return some sort of handle to the caller that can be polled, waited on, or used to fetch the return value from the operation, but I can't find a class or interface that's suitable for the task.
BackgroundWorker comes close, but it's geared to the case where the worker has its own dedicated thread, which isn't true in my case. IAsyncResult looks promising, but the provided AsyncResult implementation is also unusable for me. Should I implement IAsyncResult myself?
Clarification:
I have a class that conceptually looks like this:
class AsyncScheduler
{
private List<object> _workList = new List<object>();
private bool _finished = false;
public SomeHandle QueueAsyncWork(object workObject)
{
// simplified for the sake of example
_workList.Add(workObject);
return SomeHandle;
}
private void WorkThread()
{
// simplified for the sake of example
while (!_finished)
{
foreach (object workObject in _workList)
{
if (!workObject.IsFinished)
{
workObject.DoSomeWork();
}
}
Thread.Sleep(1000);
}
}
}
The QueueAsyncWork function pushes a work item onto the polling list for a dedicated work thread, of which there will only over be one. My problem is not with writing the QueueAsyncWork function--that's fine. My question is, what do I return to the caller? What should SomeHandle be?
The existing .Net classes for this are geared towards the situation where the asynchronous operation can be encapsulated in a single method call that returns. That's not the case here--all of the work objects do their work on the same thread, and a complete work operation might span multiple calls to workObject.DoSomeWork(). In this case, what's a reasonable approach for offering the caller some handle for progress notification, completion, and getting the final outcome of the operation?
Yes, implement IAsyncResult (or rather, an extended version of it, to provide for progress reporting).
public class WorkObjectHandle : IAsyncResult, IDisposable
{
private int _percentComplete;
private ManualResetEvent _waitHandle;
public int PercentComplete {
get {return _percentComplete;}
set
{
if (value < 0 || value > 100) throw new InvalidArgumentException("Percent complete should be between 0 and 100");
if (_percentComplete = 100) throw new InvalidOperationException("Already complete");
if (value == 100 && Complete != null) Complete(this, new CompleteArgs(WorkObject));
_percentComplete = value;
}
public IWorkObject WorkObject {get; private set;}
public object AsyncState {get {return WorkObject;}}
public bool IsCompleted {get {return _percentComplete == 100;}}
public event EventHandler<CompleteArgs> Complete; // CompleteArgs in a usual pattern
// you may also want to have Progress event
public bool CompletedSynchronously {get {return false;}}
public WaitHandle
{
get
{
// initialize it lazily
if (_waitHandle == null)
{
ManualResetEvent newWaitHandle = new ManualResetEvent(false);
if (Interlocked.CompareExchange(ref _waitHandle, newWaitHandle, null) != null)
newWaitHandle.Dispose();
}
return _waitHandle;
}
}
public void Dispose()
{
if (_waitHandle != null)
_waitHandle.Dispose();
// dispose _workObject too, if needed
}
public WorkObjectHandle(IWorkObject workObject)
{
WorkObject = workObject;
_percentComplete = 0;
}
}
public class AsyncScheduler
{
private Queue<WorkObjectHandle> _workQueue = new Queue<WorkObjectHandle>();
private bool _finished = false;
public WorkObjectHandle QueueAsyncWork(IWorkObject workObject)
{
var handle = new WorkObjectHandle(workObject);
lock(_workQueue)
{
_workQueue.Enqueue(handle);
}
return handle;
}
private void WorkThread()
{
// simplified for the sake of example
while (!_finished)
{
WorkObjectHandle handle;
lock(_workQueue)
{
if (_workQueue.Count == 0) break;
handle = _workQueue.Dequeue();
}
try
{
var workObject = handle.WorkObject;
// do whatever you want with workObject, set handle.PercentCompleted, etc.
}
finally
{
handle.Dispose();
}
}
}
}
If I understand correctly you have a collection of work objects (IWorkObject) that each complete a task via multiple calls to a DoSomeWork method. When an IWorkObject object has finished its work you'd like to respond to that somehow and during the process you'd like to respond to any reported progress?
In that case I'd suggest you take a slightly different approach. You could take a look at the Parallel Extension framework (blog). Using the framework, you could write something like this:
public void QueueWork(IWorkObject workObject)
{
Task.TaskFactory.StartNew(() =>
{
while (!workObject.Finished)
{
int progress = workObject.DoSomeWork();
DoSomethingWithReportedProgress(workObject, progress);
}
WorkObjectIsFinished(workObject);
});
}
Some things to note:
QueueWork now returns void. The reason for this is that the actions that occur when progress is reported or when the task completes have become part of the thread that executes the work. You could of course return the Task that the factory creates and return that from the method (to enable polling for example).
The progress-reporting and finish-handling are now part of the thread because you should always avoid polling when possible. Polling is more expensive because usually you either poll too frequently (too early) or not often enough (too late). There is no reason you can't report on the progress and finishing of the task from within the thread that is running the task.
The above could also be implemented using the (lower level) ThreadPool.QueueUserWorkItem method.
Using QueueUserWorkItem:
public void QueueWork(IWorkObject workObject)
{
ThreadPool.QueueUserWorkItem(() =>
{
while (!workObject.Finished)
{
int progress = workObject.DoSomeWork();
DoSomethingWithReportedProgress(workObject, progress);
}
WorkObjectIsFinished(workObject);
});
}
The WorkObject class can contain the properties that need to be tracked.
public class WorkObject
{
public PercentComplete { get; private set; }
public IsFinished { get; private set; }
public void DoSomeWork()
{
// work done here
this.PercentComplete = 50;
// some more work done here
this.PercentComplete = 100;
this.IsFinished = true;
}
}
Then in your example:
Change the collection from a List to a Dictionary that can hold Guid values (or any other means of uniquely identifying the value).
Expose the correct WorkObject's properties by having the caller pass the Guid that it received from QueueAsyncWork.
I'm assuming that you'll start WorkThread asynchronously (albeit, the only asynchronous thread); plus, you'll have to make retrieving the dictionary values and WorkObject properties thread-safe.
private Dictionary<Guid, WorkObject> _workList =
new Dictionary<Guid, WorkObject>();
private bool _finished = false;
public Guid QueueAsyncWork(WorkObject workObject)
{
Guid guid = Guid.NewGuid();
// simplified for the sake of example
_workList.Add(guid, workObject);
return guid;
}
private void WorkThread()
{
// simplified for the sake of example
while (!_finished)
{
foreach (WorkObject workObject in _workList)
{
if (!workObject.IsFinished)
{
workObject.DoSomeWork();
}
}
Thread.Sleep(1000);
}
}
// an example of getting the WorkObject's property
public int GetPercentComplete(Guid guid)
{
WorkObject workObject = null;
if (!_workList.TryGetValue(guid, out workObject)
throw new Exception("Unable to find Guid");
return workObject.PercentComplete;
}
The simplest way to do this is described here. Suppose you have a method string DoSomeWork(int). You then create a delegate of the correct type, for example:
Func<int, string> myDelegate = DoSomeWork;
Then you call the BeginInvoke method on the delegate:
int parameter = 10;
myDelegate.BeginInvoke(parameter, Callback, null);
The Callback delegate will be called once your asynchronous call has completed. You can define this method as follows:
void Callback(IAsyncResult result)
{
var asyncResult = (AsyncResult) result;
var #delegate = (Func<int, string>) asyncResult.AsyncDelegate;
string methodReturnValue = #delegate.EndInvoke(result);
}
Using the described scenario, you can also poll for results or wait on them. Take a look at the url I provided for more info.
Regards,
Ronald
If you don't want to use async callbacks, you can use an explicit WaitHandle, such as a ManualResetEvent:
public abstract class WorkObject : IDispose
{
ManualResetEvent _waitHandle = new ManualResetEvent(false);
public void DoSomeWork()
{
try
{
this.DoSomeWorkOverride();
}
finally
{
_waitHandle.Set();
}
}
protected abstract DoSomeWorkOverride();
public void WaitForCompletion()
{
_waitHandle.WaitOne();
}
public void Dispose()
{
_waitHandle.Dispose();
}
}
And in your code you could say
using (var workObject = new SomeConcreteWorkObject())
{
asyncScheduler.QueueAsyncWork(workObject);
workObject.WaitForCompletion();
}
Don't forget to call Dispose on your workObject though.
You can always use alternate implementations which create a wrapper like this for every work object, and who call _waitHandle.Dispose() in WaitForCompletion(), you can lazily instantiate the wait handle (careful: race conditions ahead), etc. (That's pretty much what BeginInvoke does for delegates.)

Categories

Resources