How to catch/observe an unhandled exception thrown from a Task - c#

I'm trying to log / report all unhandled exceptions in my app (error reporting solution). I've come across a scenario that is always unhandled. I'm wondering how would I catch this error in an unhandled manner. Please note that I've done a ton of research this morning and tried a lot of things.. Yes, I've seen this, this and many more. I'm just looking for a generic solution to log unhandled exceptions.
I have the following code inside of a console test apps main method:
Task.Factory.StartNew(TryExecute);
or
Task.Run((Action)TryExecute);
as well as the following method:
private static void TryExecute() {
throw new Exception("I'm never caught");
}
I'm already tried wiring up to the following in my app, but they are never called.
AppDomain.CurrentDomain.UnhandledException
TaskScheduler.UnobservedTaskException
In my Wpf app where I initially found this error I also wired up to these events but it was never called.
Dispatcher.UnhandledException
Application.Current.DispatcherUnhandledException
System.Windows.Forms.Application.ThreadException
The only handler that is called ever is:
AppDomain.CurrentDomain.FirstChanceException
but this is not a valid solution as I only want to report uncaught exceptions (not every exception as FirstChanceException is called before any catch blocks are ever executed / resolved.

The TaskScheduler.UnobservedTaskException event should give you what you want, as you stated above. What makes you think that it is not getting fired?
Exceptions are caught by the task and then re-thrown, but not immediately, in specific situations. Exceptions from tasks are re-thrown in several ways (off the top of my head, there are probably more).
When you try and access the result (Task.Result)
Calling Wait(), Task.WaitOne(), Task.WaitAll() or another related Wait method on the task.
When you try to dispose the Task without explicitly looking at or handling the exception
If you do any of the above, the exception will be rethrown on whatever thread that code is running on, and the event will not be called since you will be observing the exception. If you don't have the code inside of a try {} catch {}, you will fire the AppDomain.CurrentDomain.UnhandledException, which sounds like what might be happening.
The other way the exception is re-thrown would be:
When you do none of the above so that the task still views the exception as unobserved and the Task is getting finalized. It is thrown as a last ditch effort to let you know there was an exception that you didn't see.
If this is the case and since the finalizer is non-deterministic, are you waiting for a GC to happen so that those tasks with unobserved exceptions are put in the finalizer queue, and then waiting again for them to be finalized?
EDIT: This article talks a little bit about this. And this article talks about why the event exists, which might give you insight into how it can be used properly.

I used the LimitedTaskScheduler from MSDN to catch all exceptions, included from other threads using the TPL:
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
/// Whether the current thread is processing work items.
[ThreadStatic]
private static bool currentThreadIsProcessingItems;
/// The list of tasks to be executed.
private readonly LinkedList tasks = new LinkedList(); // protected by lock(tasks)
private readonly ILogger logger;
/// The maximum concurrency level allowed by this scheduler.
private readonly int maxDegreeOfParallelism;
/// Whether the scheduler is currently processing work items.
private int delegatesQueuedOrRunning; // protected by lock(tasks)
public LimitedConcurrencyLevelTaskScheduler(ILogger logger) : this(logger, Environment.ProcessorCount)
{
}
public LimitedConcurrencyLevelTaskScheduler(ILogger logger, int maxDegreeOfParallelism)
{
this.logger = logger;
if (maxDegreeOfParallelism Gets the maximum concurrency level supported by this scheduler.
public override sealed int MaximumConcurrencyLevel
{
get { return maxDegreeOfParallelism; }
}
/// Queues a task to the scheduler.
/// The task to be queued.
protected sealed override void QueueTask(Task task)
{
// Add the task to the list of tasks to be processed. If there aren't enough
// delegates currently queued or running to process tasks, schedule another.
lock (tasks)
{
tasks.AddLast(task);
if (delegatesQueuedOrRunning >= maxDegreeOfParallelism)
{
return;
}
++delegatesQueuedOrRunning;
NotifyThreadPoolOfPendingWork();
}
}
/// Attempts to execute the specified task on the current thread.
/// The task to be executed.
///
/// Whether the task could be executed on the current thread.
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// If this thread isn't already processing a task, we don't support inlining
if (!currentThreadIsProcessingItems)
{
return false;
}
// If the task was previously queued, remove it from the queue
if (taskWasPreviouslyQueued)
{
TryDequeue(task);
}
// Try to run the task.
return TryExecuteTask(task);
}
/// Attempts to remove a previously scheduled task from the scheduler.
/// The task to be removed.
/// Whether the task could be found and removed.
protected sealed override bool TryDequeue(Task task)
{
lock (tasks)
{
return tasks.Remove(task);
}
}
/// Gets an enumerable of the tasks currently scheduled on this scheduler.
/// An enumerable of the tasks currently scheduled.
protected sealed override IEnumerable GetScheduledTasks()
{
var lockTaken = false;
try
{
Monitor.TryEnter(tasks, ref lockTaken);
if (lockTaken)
{
return tasks.ToArray();
}
else
{
throw new NotSupportedException();
}
}
finally
{
if (lockTaken)
{
Monitor.Exit(tasks);
}
}
}
protected virtual void OnTaskFault(AggregateException exception)
{
logger.Error(exception);
}
///
/// Informs the ThreadPool that there's work to be executed for this scheduler.
///
private void NotifyThreadPoolOfPendingWork()
{
ThreadPool.UnsafeQueueUserWorkItem(ExcuteTask, null);
}
private void ExcuteTask(object state)
{
// Note that the current thread is now processing work items.
// This is necessary to enable inlining of tasks into this thread.
currentThreadIsProcessingItems = true;
try
{
// Process all available items in the queue.
while (true)
{
Task item;
lock (tasks)
{
// When there are no more items to be processed,
// note that we're done processing, and get out.
if (tasks.Count == 0)
{
--delegatesQueuedOrRunning;
break;
}
// Get the next item from the queue
item = tasks.First.Value;
tasks.RemoveFirst();
}
// Execute the task we pulled out of the queue
TryExecuteTask(item);
if (!item.IsFaulted)
{
continue;
}
OnTaskFault(item.Exception);
}
}
finally
{
// We're done processing items on the current thread
currentThreadIsProcessingItems = false;
}
}
}
And than the "registration" of the TaskScheduler as the default using Reflection:
public static class TaskLogging
{
private const BindingFlags StaticBinding = BindingFlags.Static | BindingFlags.NonPublic;
public static void SetScheduler(TaskScheduler taskScheduler)
{
var field = typeof(TaskScheduler).GetField("s_defaultTaskScheduler", StaticBinding);
field.SetValue(null, taskScheduler);
SetOnTaskFactory(new TaskFactory(taskScheduler));
}
private static void SetOnTaskFactory(TaskFactory taskFactory)
{
var field = typeof(Task).GetField("s_factory", StaticBinding);
field.SetValue(null, taskFactory);
}
}

Related

AsyncContext -> AsyncContextThread handling exceptions

Regarding: AsyncContextThread
https://github.com/StephenCleary/AsyncEx/wiki/AsyncContext
https://github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Context/AsyncContextThread.cs
It's not really covered how to handle catching exceptions that occur when the thread is started.
public partial class MyService : ServiceBase
{
private readonly AsyncContextThread _thread = new AsyncContextThread();
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public MyService()
{
InitializeComponent();
}
public void Start()
{
OnStart(null);
}
protected override void OnStart(string[] args)
{
try
{
_thread.Factory.Run(StartAsync);
}
catch (Exception ex)
{
// Exception?
}
}
private async Task StartAsync()
{
throw new Exception("things went wrong");
}
protected override void OnStop()
{
if (!_cts.IsCancellationRequested)
_cts.Cancel();
_thread.JoinAsync().GetAwaiter().GetResult();
}
}
I don't seem to catch anything in the catch{} block, in addition I tried adding these to my Program.cs (main entry point).
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
However neither of the event handler methods are triggered in the above code.
Q) How do you handle exceptions correctly in this situation?
As a side note, I'm fairly sure that using AsyncContext previously and debugging in Visual Studio did catch the exception, so I'm not sure if there's some sync. context I'm missing, when trying to add a handler for exceptions.
EDIT
I know I can try/catch within the StartAsync method, but the point I'm getting at is being able to catch any unhandled Exceptions and make sure I can log them.
I've been meaning to do a blog post on asynchronous Win32 services... for a few years now... :)
It's not really covered how to handle catching exceptions that occur when the thread is started.
The AsyncContextThread is actually started immediately upon creation. What you're doing on OnStart is queueing work to its context.
The exception for that work is observed on the Task returned from Run, and the appropriate way to observe it is using await:
protected override async void OnStart(string[] args)
{
try
{
await _thread.Factory.Run(StartAsync);
}
catch (Exception ex)
{
// Exception!
}
}
A few notes:
Normally, you should avoid async void. The exception to that rule is event handlers or event-like methods (such as OnStart). When you do use async void, you should consider having a try/catch around the entire async void method body (which we do).
await is superior to ContinueWith. In this particular case, ContinueWith will work OK, but in the general case it's not advisable: it does not understand async delegates, it does not use an appropriate default TaskScheduler, and it does not use appropriate default continuation flags. These are all things that await just handles for you.
You may use a Continuation to handle exceptions.
protected override void OnStart(string[] args)
{
_thread.Factory.Run(StartAsync).ContinueWith(t =>
{
var aggregate = t.Exception.Flatten();
foreach(var exception in aggregate.InnerExceptions)
{
// Handle exception
}
}, TaskContinuationOptions.OnlyOnFaulted);
}

How to solve producer/consumer race condition with BlockingCollection<>

I am implementing a logger which writes records to the database. In order to prevent the database writes from blocking the code which is calling the logger, I've moved the DB access to a separate thread, implemented using a producer/consumer model based on BlockingCollection<string>.
Here's the simplified implementation:
abstract class DbLogger : TraceListener
{
private readonly BlockingCollection<string> _buffer;
private readonly Task _writerTask;
DbLogger()
{
this._buffer = new BlockingCollection<string>(new ConcurrentQueue<string>(), 1000);
this._writerTask = Task.Factory.StartNew(this.ProcessBuffer, TaskCreationOptions.LongRunning);
}
// Enqueue the msg.
public void LogMessage(string msg) { this._buffer.Add(msg); }
private void ProcessBuffer()
{
foreach (string msg in this._buffer.GetConsumingEnumerable())
{
this.WriteToDb(msg);
}
}
protected abstract void WriteToDb(string msg);
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Signal to the blocking collection that the enumerator is done.
this._buffer.CompleteAdding();
// Wait for any in-progress writes to finish.
this._writerTask.Wait(timeout);
this._buffer.Dispose();
}
base.Dispose(disposing);
}
}
Now, when my application shuts down, I need to make sure that the buffer is flushed before the database connection goes down. Otherwise, WriteToDb will throw an exception.
So, here's my naive Flush implementation:
public void Flush()
{
// Sleep until the buffer is empty.
while(this._buffer.Count > 0)
{
Thread.Sleep(50);
}
}
The problem with this implementation is the following sequence of events:
There is one entry in the buffer.
In the logging thread, MoveNext() is called on the enumerator, so we're now in the body of ProcessBuffer's foreach loop.
Flush() is called by the main thread. It sees that the collection is empty, so returns immediately.
The main thread closes the DB connection.
Back in the logging thread, the body of the foreach loop starts executing. WriteToDb is called, and fails because the DB connection has been closed.
So, my next try was adding some flags, like so:
private volatile bool _isWritingBuffer = false;
private void ProcessBuffer()
{
foreach (string msg in this._buffer.GetConsumingEnumerable())
{
lock (something) this._isWritingBuffer = true;
this.WriteToDb(msg);
lock (something) this._isWritingBuffer = false;
}
}
public void Flush()
{
// Sleep until the buffer is empty.
bool isWritingBuffer;
lock(something) isWritingBuffer = this._isWritingBuffer;
while(this._buffer.Count > 0 || isWritingBuffer)
{
Thread.Sleep(50);
}
}
However, there's still a race condition, since the entire Flush() method could execute after the collection is empty but before _isWritingBuffer gets set to true.
How can I fix my Flush implementation to avoid this race condition?
Note: For various reasons, I must write the logger from scratch, so please don't answer with a suggestion that I use some existing logging framework.
First never ever lock on a public object, especially this.
Moreover never ever use bare booleans for synchronization: see my blog if you want to have a glimpse in what can go wrong: Synchronization, memory visibility and leaky abstractions :)
Concerning the issue itself I must be missing something but why do you need such a Flush method?
Indeed when you're done with your logging you will dispose of the logger by calling its Dispose method from the main thread.
And you have implemented it in such a way that it will wait for the "write to DB" task.
If I'm wrong and you really need to synchronize with another primitive then you should use an event:
In the DbLogger:
public ManualResetEvent finalizing { get; set; }
public void Flush()
{
finalizing.WaitOne();
}
And somewhere, e.g. in ProcessBuffer you notify when you're done with writing to DB:
finalizing.Set();

How does nunit successfully wait for async void methods to complete?

When using async/await in C#, the general rule is to avoid async void as that's pretty much a fire and forget, rather a Task should be used if no return value is sent from the method. Makes sense. What's strange though is that earlier in the week I was writing some unit tests for a few async methods I wrote, and noticed that NUnit suggested to mark the async tests as either void or returning Task. I then tried it, and sure enough, it worked. This seemed really strange, as how would the nunit framework be able to run the method and wait for all asynchronous operations to complete? If it returns Task, it can just await the task, and then do what it needs to do, but how can it pull it off if it returns void?
So I cracked open the source code and found it. I can reproduce it in a small sample, but I simply cannot make sense of what they're doing. I guess I don't know enough about the SynchronizationContext and how that works. Here's the code:
class Program
{
static void Main(string[] args)
{
RunVoidAsyncAndWait();
Console.WriteLine("Press any key to continue. . .");
Console.ReadKey(true);
}
private static void RunVoidAsyncAndWait()
{
var previousContext = SynchronizationContext.Current;
var currentContext = new AsyncSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(currentContext);
try
{
var myClass = new MyClass();
var method = myClass.GetType().GetMethod("AsyncMethod");
var result = method.Invoke(myClass, null);
currentContext.WaitForPendingOperationsToComplete();
}
finally
{
SynchronizationContext.SetSynchronizationContext(previousContext);
}
}
}
public class MyClass
{
public async void AsyncMethod()
{
var t = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Done sleeping!");
});
await t;
Console.WriteLine("Done awaiting");
}
}
public class AsyncSynchronizationContext : SynchronizationContext
{
private int _operationCount;
private readonly AsyncOperationQueue _operations = new AsyncOperationQueue();
public override void Post(SendOrPostCallback d, object state)
{
_operations.Enqueue(new AsyncOperation(d, state));
}
public override void OperationStarted()
{
Interlocked.Increment(ref _operationCount);
base.OperationStarted();
}
public override void OperationCompleted()
{
if (Interlocked.Decrement(ref _operationCount) == 0)
_operations.MarkAsComplete();
base.OperationCompleted();
}
public void WaitForPendingOperationsToComplete()
{
_operations.InvokeAll();
}
private class AsyncOperationQueue
{
private bool _run = true;
private readonly Queue _operations = Queue.Synchronized(new Queue());
private readonly AutoResetEvent _operationsAvailable = new AutoResetEvent(false);
public void Enqueue(AsyncOperation asyncOperation)
{
_operations.Enqueue(asyncOperation);
_operationsAvailable.Set();
}
public void MarkAsComplete()
{
_run = false;
_operationsAvailable.Set();
}
public void InvokeAll()
{
while (_run)
{
InvokePendingOperations();
_operationsAvailable.WaitOne();
}
InvokePendingOperations();
}
private void InvokePendingOperations()
{
while (_operations.Count > 0)
{
AsyncOperation operation = (AsyncOperation)_operations.Dequeue();
operation.Invoke();
}
}
}
private class AsyncOperation
{
private readonly SendOrPostCallback _action;
private readonly object _state;
public AsyncOperation(SendOrPostCallback action, object state)
{
_action = action;
_state = state;
}
public void Invoke()
{
_action(_state);
}
}
}
When running the above code, you'll notice that the Done Sleeping and Done awaiting messages show up before the Press any key to continue message, which means the async method is somehow being waited on.
My question is, can someone care to explain what's happening here? What exactly is the SynchronizationContext (I know it's used to post work from one thread to another) but I'm still confused as to how we can wait for all the work to be done. Thanks in advance!!
A SynchronizationContext allows posting work to a queue that is processed by another thread (or by a thread pool) -- usually the message loop of the UI framework is used for this.
The async/await feature internally uses the current synchronization context to return to the correct thread after the task you were waiting for has completed.
The AsyncSynchronizationContext class implements its own message loop. Work that is posted to this context gets added to a queue.
When your program calls WaitForPendingOperationsToComplete();, that method runs a message loop by grabbing work from the queue and executing it.
If you set a breakpoint on Console.WriteLine("Done awaiting");, you will see that it runs on the main thread within the WaitForPendingOperationsToComplete() method.
Additionally, the async/await feature calls the OperationStarted() / OperationCompleted() methods to notify the SynchronizationContext whenever an async void method starts or finishes executing.
The AsyncSynchronizationContext uses these notifications to keep a count of the number of async methods that are running and haven't completed yet. When this count reaches zero, the WaitForPendingOperationsToComplete() method stops running the message loop, and the control flow returns to the caller.
To view this process in the debugger, set breakpoints in the Post, OperationStarted and OperationCompleted methods of the synchronization context. Then step through the AsyncMethod call:
When AsyncMethod is called, .NET first calls OperationStarted()
This sets the _operationCount to 1.
Then the body of AsyncMethod starts running (and starts the background task)
At the await statement, AsyncMethod yields control as the task is not yet complete
currentContext.WaitForPendingOperationsToComplete(); gets called
No operations are available in the queue yet, so the main thread goes to sleep at _operationsAvailable.WaitOne();
On the background thread:
at some point the task finishes sleeping
Output: Done sleeping!
the delegate finishes execution and the task gets marked as complete
the Post() method gets called, enqueuing a continuation that represents the remainder of the AsyncMethod
The main thread wakes up because the queue is no longer empty
The message loop runs the continuation, thus resuming execution of AsyncMethod
Output: Done awaiting
AsyncMethod finishes execution, causing .NET to call OperationComplete()
the _operationCount is decremented to 0, which marks the message loop as complete
Control returns to the message loop
The message loop finishes because it was marked as complete, and WaitForPendingOperationsToComplete returns to the caller
Output: Press any key to continue. . .

Is it possible to fail triggering the AppDomain's unhandled exception handler from a Task continuation?

I have a Task which runs asynchronously and handles exceptions using a task continuation task.ContinueWith(t => ..., CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, taskScheduler).
This works great for handling exceptions I know how to handle (e.g. WebException), but what if something like NullReferenceException is thrown, which I can't handle correctly?
I want to be able to handle it using the AppDomain's unhandled exception handler (which in our case logs the error, pops up a dialog notifying the user, and exits the application), but so far I have not found a good way to do this.
Simply rethrowing the exception from the continuation results in the unhandled exception handler being called when the task is finalized (because the rethrown exception is unobserved). This is not good because it may be seconds or even minutes after I know the application should crash.
Calling Environment.FailFast() does crash the application but does not call the unhandled exception handler.
Calling Thread.CurrentThread.Abort() calls the unhandled exception handler but only displays information/the stack trace from the ThreadAbortException. Plus it just seems like a bad idea to use this method.
Calling Application.OnThreadException() actually does exactly what I want, except that it requires referencing System.Windows.Forms and handling Application.ThreadException which will not work with, for example, a service with no UI.
Calling task.Wait() does not make sense because this in our case there's no place to call it from. If the task succeeds the result is processed using a success continuation, if it fails the exception continuation is called, and we can't block the thread creating the task (that's the whole point of the task).
I could call the unhandled exception handler directly except that in at least one case that would require adding a dependency I don't want to add in my application.
Am I missing an obvious way to do this?
I didn't find any BCL class which does similar work. So, I can suggest you to use some hand-written static class:
static class Crusher
{
private static readonly AutoResetEvent CrushEvent;
private static Exception _exception;
static Crusher()
{
CrushEvent = new AutoResetEvent(false);
}
public static Thread GetCrushWaitingThread()
{
return new Thread(WaitForCrush);
}
static void WaitForCrush()
{
CrushEvent.WaitOne();
throw _exception;
}
public static void Crush(Exception exception)
{
_exception = exception;
CrushEvent.Set();
}
}
You should just init it at your application startup:
static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
Crusher.GetCrushWaitingThread().Start();
...
and then - you can call it from continuations:
task.ContinueWith(Continuation, TaskContinuationOptions.OnlyOnFaulted);
...
private static void Continuation(Task obj)
{
Crusher.Crush(obj.Exception);
}
But it's not as nice as real rethrown unhandled exception (it has addition information about Crusher.WaitForCrush() method in stacktrace).
We used Application.OnThreadException() to solve our specific case.
Just use the same logic that you implemented for AppDomain.UnhandledException and call it explicitly when you hit an exception in your continuation. Here is an example crash handler I use:
public static class CrashHandler
{
public static void Setup()
{
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
}
public static Task CrashOnUnhandledException(this Task t)
{
t.ContinueWith(x => HandleException(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
return t;
}
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
HandleException(e.ExceptionObject);
}
private static void HandleException(object ex)
{
ShowErrorMessage(ex);
Environment.Exit(-1);
}
private static void ShowErrorMessage(object ex)
{
// your crash dialog goes here
}
}
The extension method CrashOnUnhandledException can be used like this to save you some typing and give you additional information about the call site in your stack trace:
task.ContinueWith(t => { /* your logic */ })
.CrashOnUnhandledException();

Serial Task Executor; is this thread safe?

I have a class that I've created to allow asynchronous sequential execution of tasks, using the ThreadPool as the means of execution. The idea is that I'll have multiple instances running serial tasks in the background, but I don't want to have a separate dedicated Thread for each instance. What I'd like to check is whether this class is actually thread safe. It's fairly brief, so I thought I'd run it by the experts here, in case I'm missing something obvious. I've omitted a few of the convenience overloads for different Action types.
/// <summary>
/// This class wraps ThreadPool.QueueUserWorkItem, but providing guaranteed ordering of queued tasks for this instance.
/// Only one task in the queue will execute at a time, with the order of execution matching the order of addition.
/// This is designed as a lighter-weight alternative to using a dedicated Thread for processing of sequential tasks.
/// </summary>
public sealed class SerialAsyncTasker
{
private readonly Queue<Action> mTasks = new Queue<Action>();
private bool mTaskExecuting;
/// <summary>
/// Queue a new task for asynchronous execution on the thread pool.
/// </summary>
/// <param name="task">Task to execute</param>
public void QueueTask(Action task)
{
if (task == null) throw new ArgumentNullException("task");
lock (mTasks)
{
bool isFirstTask = (mTasks.Count == 0);
mTasks.Enqueue(task);
//Only start executing the task if this is the first task
//Additional tasks will be executed normally as part of sequencing
if (isFirstTask && !mTaskExecuting)
RunNextTask();
}
}
/// <summary>
/// Clear all queued tasks. Any task currently executing will continue to execute.
/// </summary>
public void Clear()
{
lock (mTasks)
{
mTasks.Clear();
}
}
/// <summary>
/// Wait until all currently queued tasks have completed executing.
/// If no tasks are queued, this method will return immediately.
/// This method does not prevent the race condition of a second thread
/// queueing a task while one thread is entering the wait;
/// if this is required, it must be synchronized externally.
/// </summary>
public void WaitUntilAllComplete()
{
lock (mTasks)
{
while (mTasks.Count > 0 || mTaskExecuting)
Monitor.Wait(mTasks);
}
}
private void RunTask(Object state)
{
var task = (Action)state;
task();
mTaskExecuting = false;
RunNextTask();
}
private void RunNextTask()
{
lock (mTasks)
{
if (mTasks.Count > 0)
{
mTaskExecuting = true;
var task = mTasks.Dequeue();
ThreadPool.QueueUserWorkItem(RunTask, task);
}
else
{
//If anybody is waiting for tasks to be complete, let them know
Monitor.PulseAll(mTasks);
}
}
}
}
UPDATE: I've revised the code to fix the main bugs kindly pointed out by Simon. This passes unit tests now, but I still welcome observations.
Don't do it. (Or at least avoid building your own stuff.)
Use the System.Threading.Tasks stuff (new in .NET 4.0). Create your a Task[] (size depends on number of parallel tasks you want) and let them read work items from a BlockingCollection while waiting for a CancellationToken. Your WaitForAll implementation would trigger your token, and call Task.WaitAll(Task[]) which will block until all your tasks are done.
Here's my second answer assuming that you cant use .NET 4.0 (and want comments on your existing code).
QueueTask enqueues the first task, getting isFirstTask = true, and starts a new thread. However, another thread may enqueue something while the first thread is processing, and Count == 0 => isFirstTask = true, and yet another thread is spawned.
Also, WaitUntilAllComplete will hang indefinitely if the task execution throws an exception (which may not necessarily crash everything, depending on exception handling), causing it to skip the call to RunNextTask().
And your WaitUntilAllComplete just waits until there are no more enqueue tasks, not that those currently executing are actually executing (they could just be enqueued in the ThreadPool) or complete.
It's built in in 4.0
How to: Create a Task Scheduler That Limits the Degree of Concurrency
You can also use a custom scheduler to achieve functionality that the default scheduler does not provide, such as strict first-in, first-out (FIFO) execution order. The following example demonstrates how to create a custom task scheduler. This scheduler lets you specify the degree of concurrency.
I see a few issues your with your SerialAsyncTasker class, but it sounds like you might have a good grasp of those so I will not go into any details on that topic (I may edit my answer with more details later). You indicated in the comments that you cannot use .NET 4.0 features nor can you use the Reactive Extensions backport. I propose that you use the producer-consumer pattern with a single consumer on a dedicated thread. This would perfectly fit your requirement of asynchronously executing tasks sequentially.
Note: You will have to harden the code to support gracefully shutting down, handling exceptions, etc.
public class SerialAsyncTasker
{
private BlockingCollection<Action> m_Queue = new BlockingCollection<Action>();
public SerialAsyncTasker()
{
var thread = new Thread(
() =>
{
while (true)
{
Action task = m_Queue.Take();
task();
}
});
thread.IsBackground = true;
thread.Start();
}
public void QueueTask(Action task)
{
m_Queue.Add(task);
}
}
Too bad you cannot use the BlockingCollection from the .NET 4.0 BCL or Reactive Extension download, but no worries. It is actually not too hard to implement one yourself. You can use Stephen Toub's blocking queue as a starting point and just rename a few things.
public class BlockingCollection<T>
{
private Queue<T> m_Queue = new Queue<T>();
public T Take()
{
lock (m_Queue)
{
while (m_Queue.Count <= 0) Monitor.Wait(m_Queue);
return m_Queue.Dequeue();
}
}
public void Add(T value)
{
lock (m_Queue)
{
m_Queue.Enqueue(value);
Monitor.Pulse(m_Queue);
}
}
}
public class ParallelExcecuter
{
private readonly BlockingCollection<Task> _workItemHolder;
public ParallelExcecuter(int maxDegreeOfParallelism)
{
_workItemHolder = new BlockingCollection<Task>(maxDegreeOfParallelism);
}
public void Submit(Action action)
{
_workItemHolder.Add(Task.Run(action).ContinueWith(t =>
{
_workItemHolder.Take();
}));
}
public void WaitUntilWorkDone()
{
while (_workItemHolder.Count < 0)
{
Monitor.Wait(_workItemHolder);
}
}
}

Categories

Resources