TPL execute Dependent task and stop all task when some task fails - c#

I have a list of task and few task are dependent on others, I would like to run the tasks now and if any of the task fails during execution, need to stop all the running task and close the application.
How can do this with TPL?
How to stop the running task?
I need to optimize the below code.
Detailed Requirement
- Start the Logon screen as a task.
o Run all other task in parallel only if the Logon is successful.
o Exit application in case of Logon failure or Cancel
- Exit application if any of the task fails
var done = new List<TaskDetails>();
var executing = new List<TaskDetails>();
var unblocked = new List<TaskDetails>();
var blocked = new List<TaskDetails>();
foreach (var startupTest in startupTests) {
if (startupTest.DependsOn == null) {
unblocked.Add(startupTest);
} else {
blocked.Add(startupTest);
}
}
IDictionary<int, TaskDetails> tasksByID = new Dictionary<int, TaskDetails>();
var tasksTPL = new Task<object>[startupTests.Count];
var taskCount = 0;
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
while (done.Count < startupTests.Count) {
while (executing.Count < config.MaximumConcurrency && unblocked.Count > 0) {
TaskDetails nextTask = unblocked[0];
lock (syncLock) {
unblocked.Remove(nextTask);
executing.Add(nextTask);
}
// Execute
try {
var method = GetMethod(
nextTask.AssemblyName, nextTask.ClassName, nextTask.MethodName
);
if (method == null) {
throw new Exception("Method" + nextTask.MethodName + " not available.");
}
tasksTPL[taskCount] =
Task<object>.Factory.StartNew(() => method.Invoke(null, null),
cancellationToken);
tasksByID.Add(tasksTPL[taskCount].Id, nextTask);
tasksTPL[taskCount].ContinueWith(tsk => {
lock (syncLock) {
done.Add(tasksByID[tsk.Id]);
executing.Remove(tasksByID[tsk.Id]);
}
if (tsk.Exception != null) {
TraceAlways(
"Caught Exception while running startuptest: " +
tsk.Exception
);
}
});
taskCount++;
} catch (TargetInvocationException e) {
TraceAlways(
"Failed running " + nextTask.MethodName + " method." + e.Message);
}
}
Task.WaitAny(tasksTPL.Where(task => task != null).ToArray());
var toRemove = new List<TaskDetails>();
lock (syncLock) {
List<string> doneTaskName =
done.Select(TaskDetails => TaskDetails.Name).ToList();
foreach (var task in blocked) {
bool isBlocked = task.DependsOn.Any(dep => !doneTaskName.Contains(dep));
if (!isBlocked) {
toRemove.Add(task);
unblocked.Add(task);
}
}
foreach (var TaskDetails in toRemove) {
blocked.Remove(TaskDetails);
}
}
if (executing.Count == 0 && unblocked.Count == 0 && blocked.Count > 0) {
throw new Exception("Cyclic Dependency");
}
}
taskCount = 0;
foreach (var task in tasksTPL) {
if (
(task.Status != TaskStatus.Faulted) &&
(task.Result is bool) &&
(!(bool)task.Result)
) {
TraceAlways("Startup Test" + startupTests[taskCount].MethodName + " failed.");
if (startupTests[taskCount].ShowNotification) {
cancellationTokenSource.Cancel();
MessageBox.Show(
"An error has accoured. See log for more details.", "Startup Error"
);
}
Environment.Exit(0);
break;
}
taskCount++;
}

Below is how I would implement it conceptually (if I understood the question correctly), although I did not attempt to meet all of your detailed requirements.
The code doesn't use ContinueWith.
DoTaskAsync is an individual independent task.
DoTaskSequenceAsync is a sequence of tasks, where some tasks depend on the result of the others.
BadTaskAsync is an example of the throwing tasks, its failure should cancel all other pending tasks.
WrapAsync wraps a task with try/catch to catch the task's exceptions and raise the global cancellation from inside.
All tasks also support the global cancellation from outside.
You can compile and try it as a console app.
using System;
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
namespace MultipleTasks
{
class Program
{
class Worker
{
// a single async Task
async Task<object> DoTaskAsync(string id, CancellationToken token, int delay)
{
Console.WriteLine("Task: " + id);
await Task.Delay(delay, token); // do some work
return id;
}
// DoTaskSequenceAsync depends on Task1, Task2, Task3
async Task<object> DoTaskSequenceAsync(string id, CancellationToken token)
{
Console.WriteLine("Task: " + id);
await DoTaskAsync(id + "." + "Task1", token, 1000);
await DoTaskAsync(id + "." + "Task2", token, 2000);
await DoTaskAsync(id + "." + "Task3", token, 3000);
// do more
return id;
}
// a bad task which throws
async Task<object> BadTaskAsync(string id, CancellationToken token, int delay)
{
Console.WriteLine("Task: " + id);
await Task.Delay(delay, token);
throw new ApplicationException(id);
}
// wraps a task and requests the cancellation if the task has failed
async Task<T> WrapAsync<T>(CancellationTokenSource cts,
Func<CancellationToken, Task<T>> taskFactory)
{
try
{
return await taskFactory(cts.Token);
}
catch
{
if (!cts.IsCancellationRequested)
{
cts.Cancel(); // cancel the others
}
throw; // rethrow
}
}
// run all tasks
public async Task DoWorkAsync(CancellationToken outsideCt)
{
var tasks = new List<Task<object>>();
var cts = new CancellationTokenSource();
ExceptionDispatchInfo capturedException = null;
try
{
using (outsideCt.Register(() => cts.Cancel()))
{
// these tasks run in parallel
tasks.Add(WrapAsync(cts, (token) => DoTaskAsync("Task1", token, 500)));
tasks.Add(WrapAsync(cts, (token) => DoTaskSequenceAsync("Sequence1", token)));
tasks.Add(WrapAsync(cts, (token) => DoTaskAsync("Task2", token, 1000)));
tasks.Add(WrapAsync(cts, (token) => BadTaskAsync("BadTask", token, 1200)));
tasks.Add(WrapAsync(cts, (token) => DoTaskSequenceAsync("Sequence2", token)));
tasks.Add(WrapAsync(cts, (token) => DoTaskAsync("Task3", token, 1500)));
await Task.WhenAll(tasks.ToArray());
}
}
catch (Exception e)
{
capturedException = ExceptionDispatchInfo.Capture(e);
}
if (outsideCt.IsCancellationRequested)
{
Console.WriteLine("Cancelled from outside.");
return;
}
if (cts.IsCancellationRequested || capturedException != null)
{
if (cts.IsCancellationRequested)
{
Console.WriteLine("Cancelled by a failed task.");
// find the failed task in tasks or via capturedException
}
if (capturedException != null && capturedException.SourceException != null)
{
Console.WriteLine("Source exception: " + capturedException.SourceException.ToString());
// could rethrow the original exception:
// capturedException.Throw();
}
}
Console.WriteLine("Results:");
tasks.ForEach((task) =>
Console.WriteLine(String.Format("Status: {0}, result: {1}",
task.Status.ToString(),
task.Status == TaskStatus.RanToCompletion? task.Result.ToString(): String.Empty)));
}
}
static void Main(string[] args)
{
var cts = new CancellationTokenSource(10000);
new Worker().DoWorkAsync(cts.Token).Wait();
Console.WriteLine("Done.");
Console.ReadLine();
}
}
}

Related

Second operation started on this context before a previous asynchronous operation completed

public async Task<JobViewModel> Handle(AddJobCommand command, CancellationToken cancellationToken)
{
if (command.JobViewModel == null) throw new InvalidOperationException("Empty request.");
var jobViewModel = command.JobViewModel;
try
{
var job = _mapper.Map<DataAccess.Domain.Lab.Job>(jobViewModel);
_context.Set<DataAccess.Domain.Lab.Job>().Add(job);
if (job.Notes!= null)
{
var newNote = job.Notes.FirstOrDefault(n => n.IsNew);
if (newNote != null)
{
newNote.JobId = job.Id;
_context.Set<DataAccess.Domain.Lab.JobNote>().Attach(newNote);
_context.Entry(newNote).State = EntityState.Added;
}
}
await OnBeforeAdd(job); // job.Name = await GenerateJobName();
await _context.SaveChangesAsync();
jobViewModel.Id = job.Id;
return jobViewModel;
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}
catch (DbUpdateException e)
{
Console.WriteLine(e.Message);
throw;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
throw;
}
}
protected async Task OnBeforeAdd(DataAccess.Domain.Lab.Job job)
{
if (string.IsNullOrWhiteSpace(job.Name))
{
job.Name = await GenerateJobName();
}
}
private async Task<string> GenerateJobName()
{
Func<int, int, string> composeName = (year, number) => string.Format("S{0:D2}{1:D3}", year, number);
int currentYear = DateTime.UtcNow.Year % 100;
int newJobNumber = 501;
//if (_context.Jobs!= null)
// {
// string maxJobNumber = await _context.Jobs.MaxAsync(j => j.Name);
string maxJobNumber = await _context.Jobs.MaxAsync(j=>j.Name);
if (!string.IsNullOrWhiteSpace(maxJobNumber))
{
var matches = Regex.Matches(maxJobNumber, "S" + currentYear + "(?<num>[0-9]{3})");
if (matches.Count == 1)
{
newJobNumber = int.Parse(matches[0].Groups["num"].Value) + 1;
}
}
// }
string newName = composeName(currentYear, newJobNumber);
while (await _context.Jobs.AnyAsync(j => j.Name == newName))
{
newJobNumber++;
newName = composeName(currentYear, newJobNumber);
}
return newName;
}
}
}
Below line give error:
string maxJobNumber = await _context.Jobs.MaxAsync(j=>j.Name);
On running the code it throws below exception "A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe."
Also the same line "string maxJobNumber = await _context.Jobs.MaxAsync(j=>j.Name);" shows warning message as
converting null literal or possible null value to non-nullable type string error . Can this be an issue?
Some comments about the above code:
There's no need for these lines:
if (job.Notes!= null)
{
var newNote = job.Notes.FirstOrDefault(n => n.IsNew);
if (newNote != null)
{
newNote.JobId = job.Id;
_context.Set<DataAccess.Domain.Lab.JobNote>().Attach(newNote);
_context.Entry(newNote).State = EntityState.Added;
}
}
Once you've added the job to the DbSet, EF will automatically figure out that its JobNotes need to be added as well. And, it will determine there state appropriately.
This is the pattern I would use:
public async Task<JobViewModel> Handle(AddJobCommand command, CancellationToken cancellationToken)
{
var jobViewModel = command.JobViewModel;
var job = _mapper.Map<DataAccess.Domain.Lab.Job>(jobViewModel);
job.Name = await GenerateJobName(job.Name);
await _context.Set<DataAccess.Domain.Lab.Job>().AddAsync(job);
await _context.SaveChangesAsync();
jobViewModel.Id = job.Id;
return jobViewModel;
}
private async Task<string> GenerateJobName(DataAccess.Domain.Lab.Job job)
{
if (!string.IsNullOrWhiteSpace(job.Name)) return job.Name;
...
}
You may be overthinking all the try-catch stuff. It is more than half the code and is obscuring the real logic. I would move that out to a higher location and concentrate on making this do the one thing it's intended to do.
The reason the code performs differently in PROD is due to a race condition. There's no debugger in PROD, so your first calls are finishing with enough time to start the second calls.

How to get the first value that specifies a condition out of multiple tasks [duplicate]

I want to execute several asynchronous tasks concurrently. Each task will run an HTTP request that can either complete successfully or throw an exception. I need to await until the first task completes successfully, or until all the tasks have failed.
How can I implement an overload of the Task.WhenAny method that accepts a predicate, so that I can exclude the non-successfully completed tasks?
Wait for any task and return the task if the condition is met. Otherwise wait again for the other tasks until there is no more task to wait for.
public static async Task<Task> WhenAny( IEnumerable<Task> tasks, Predicate<Task> condition )
{
var tasklist = tasks.ToList();
while ( tasklist.Count > 0 )
{
var task = await Task.WhenAny( tasklist );
if ( condition( task ) )
return task;
tasklist.Remove( task );
}
return null;
}
simple check for that
var tasks = new List<Task> {
Task.FromException( new Exception() ),
Task.FromException( new Exception() ),
Task.FromException( new Exception() ),
Task.CompletedTask, };
var completedTask = WhenAny( tasks, t => t.Status == TaskStatus.RanToCompletion ).Result;
if ( tasks.IndexOf( completedTask ) != 3 )
throw new Exception( "not expected" );
public static Task<T> GetFirstResult<T>(
ICollection<Func<CancellationToken, Task<T>>> taskFactories,
Predicate<T> predicate) where T : class
{
var tcs = new TaskCompletionSource<T>();
var cts = new CancellationTokenSource();
int completedCount = 0;
// in case you have a lot of tasks you might need to throttle them
//(e.g. so you don't try to send 99999999 requests at the same time)
// see: http://stackoverflow.com/a/25877042/67824
foreach (var taskFactory in taskFactories)
{
taskFactory(cts.Token).ContinueWith(t =>
{
if (t.Exception != null)
{
Console.WriteLine($"Task completed with exception: {t.Exception}");
}
else if (predicate(t.Result))
{
cts.Cancel();
tcs.TrySetResult(t.Result);
}
if (Interlocked.Increment(ref completedCount) == taskFactories.Count)
{
tcs.SetException(new InvalidOperationException("All tasks failed"));
}
}, cts.Token);
}
return tcs.Task;
}
Sample usage:
using System.Net.Http;
var client = new HttpClient();
var response = await GetFirstResult(
new Func<CancellationToken, Task<HttpResponseMessage>>[]
{
ct => client.GetAsync("http://microsoft123456.com", ct),
ct => client.GetAsync("http://microsoft123456.com", ct),
ct => client.GetAsync("http://microsoft123456.com", ct),
ct => client.GetAsync("http://microsoft123456.com", ct),
ct => client.GetAsync("http://microsoft123456.com", ct),
ct => client.GetAsync("http://microsoft123456.com", ct),
ct => client.GetAsync("http://microsoft123456.com", ct),
ct => client.GetAsync("http://microsoft.com", ct),
ct => client.GetAsync("http://microsoft123456.com", ct),
ct => client.GetAsync("http://microsoft123456.com", ct),
},
rm => rm.IsSuccessStatusCode);
Console.WriteLine($"Successful response: {response}");
public static Task<Task<T>> WhenFirst<T>(IEnumerable<Task<T>> tasks, Func<Task<T>, bool> predicate)
{
if (tasks == null) throw new ArgumentNullException(nameof(tasks));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
var tasksArray = (tasks as IReadOnlyList<Task<T>>) ?? tasks.ToArray();
if (tasksArray.Count == 0) throw new ArgumentException("Empty task list", nameof(tasks));
if (tasksArray.Any(t => t == null)) throw new ArgumentException("Tasks contains a null reference", nameof(tasks));
var tcs = new TaskCompletionSource<Task<T>>();
var count = tasksArray.Count;
Action<Task<T>> continuation = t =>
{
if (predicate(t))
{
tcs.TrySetResult(t);
}
if (Interlocked.Decrement(ref count) == 0)
{
tcs.TrySetResult(null);
}
};
foreach (var task in tasksArray)
{
task.ContinueWith(continuation);
}
return tcs.Task;
}
Sample usage:
var task = await WhenFirst(tasks, t => t.Status == TaskStatus.RanToCompletion);
if (task != null)
var value = await task;
Note that this doesn't propagate exceptions of failed tasks (just as WhenAny doesn't).
You can also create a version of this for the non-generic Task.
Here is an attempted improvement of the excellent Eli Arbel's answer. These are the improved points:
An exception in the predicate is propagated as a fault of the returned task.
The predicate is not called after a task has been accepted as the result.
The predicate is executed in the original SynchronizationContext. This makes it possible to access UI elements (if the WhenFirst method is called from a UI thread)
The source IEnumerable<Task<T>> is enumerated directly, without being converted to an array first.
public static Task<Task<T>> WhenFirst<T>(IEnumerable<Task<T>> tasks,
Func<Task<T>, bool> predicate)
{
if (tasks == null) throw new ArgumentNullException(nameof(tasks));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
var tcs = new TaskCompletionSource<Task<T>>(
TaskCreationOptions.RunContinuationsAsynchronously);
var pendingCount = 1; // The initial 1 represents the enumeration itself
foreach (var task in tasks)
{
if (task == null) throw new ArgumentException($"The {nameof(tasks)}" +
" argument included a null value.", nameof(tasks));
Interlocked.Increment(ref pendingCount);
HandleTaskCompletion(task);
}
if (Interlocked.Decrement(ref pendingCount) == 0) tcs.TrySetResult(null);
return tcs.Task;
async void HandleTaskCompletion(Task<T> task)
{
try
{
await task; // Continue on the captured context
}
catch { } // Ignore exception
if (tcs.Task.IsCompleted) return;
try
{
if (predicate(task))
tcs.TrySetResult(task);
else
if (Interlocked.Decrement(ref pendingCount) == 0)
tcs.TrySetResult(null);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
}
Another way of doing this, very similar to Sir Rufo's answer, but using AsyncEnumerable and Ix.NET
Implement a little helper method to stream any task as soon as it's completed:
static IAsyncEnumerable<Task<T>> WhenCompleted<T>(IEnumerable<Task<T>> source) =>
AsyncEnumerable.Create(_ =>
{
var tasks = source.ToList();
Task<T> current = null;
return AsyncEnumerator.Create(
async () => tasks.Any() && tasks.Remove(current = await Task.WhenAny(tasks)),
() => current,
async () => { });
});
}
One can then process the tasks in completion order, e.g. returning the first matching one as requested:
await WhenCompleted(tasks).FirstOrDefault(t => t.Status == TaskStatus.RanToCompletion)
Just wanted to add on some of the answers #Peebo and #SirRufo that are using List.Remove (because I can't comment yet)
I would consider using:
var tasks = source.ToHashSet();
instead of:
var tasks = source.ToList();
so removing would be more efficient

How to avoid dead lock in Task.WaitAll

I have multiple running tasks in Console application. In my task I call some method from EWS Managed API. So When EWS throw exception code execution is not return to WaitAll.
while (index < messageData.Count)
{
var mess = messageData[index];
var task = new System.Threading.Tasks.Task<EventsCreationResult>(() =>
{
EventsCreationResult taskResult = null;
taskResult = CreateEmail(mess, Service);
return taskResult;
});
task.Start();
tasks.Add(task);
index = index + 1;
}
System.Threading.Tasks.Task.WaitAll(tasks.ToArray());
And Function
public static EventsCreationResult CreateEmail(string folderName, EmailMessage mess)
{
EventsCreationResult result = new EventsCreationResult();
result.Key = mess.Subject;
try
{
mess.Save(FindFolder(folderName).Id);
result.EventsCreationLog = "Message created:" + mess.Subject;
}
catch (Exception ex)
{
result.Error = ex;
}
return result;
}
So how to avoid dead lock and if its possible do not cancel task which not rise error.

Stop semphore task execution if exception occurs in current running task

I have the following test code to simulate the use of a semaphore and throttling task execution. Is there a way to not continue to create new tasks if one of the running tasks throws an exception like the below. I don't need the existing tasks to stop running, I just want no new tasks to start after the exception is encountered.
Currently the tasks will all start in this scenario below. I want it to stop after a few tasks are ran because of the exceptions being thrown.
var testStrings = new List<string>();
for (var i = 0; i < 5000; i++)
{
testStrings.Add($"string-{i}");
}
using (var semaphore = new SemaphoreSlim(10))
{
var tasks = testStrings.Select(async testString =>
{
await semaphore.WaitAsync();
try
{
Console.WriteLine($"{testString}-Start");
await Task.Delay(2000);
throw new Exception("test");
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
}
As per Sinatr's comments, I think this may work for me by adding a cancellation token to be monitored.
var testStrings = new List<string>();
for (var i = 0; i < 5000; i++)
{
testStrings.Add($"string-{i}");
}
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
using (var semaphore = new SemaphoreSlim(10))
{
var tasks = testStrings.Select(async testString =>
{
await semaphore.WaitAsync();
try
{
if (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine($"{testString}-Start");
await Task.Delay(2000);
throw new Exception("test");
}
}
catch (Exception ex)
{
if (!cancellationTokenSource.IsCancellationRequested)
cancellationTokenSource.Cancel();
throw;
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
}

Async Await - Need some guidance

I have tried many different ways to get this to work, and I am sure that is not the proper way to wire up async/await for multi threading. Here is what I have so far. It is a directory walker that I attempted to make async. I know that you don't see any async or await keywords and that is because I was unsuccessful, but that is what I am trying to do. Right now it runs in a console application but I will abstract and refactor later once I get a working POC. Any guidance is appreciated.
static void RunProgram(CancellationToken ct)
{
try
{
foreach (var dir in _directoriesToProcess)
{
var newTask = CreateNewTask(dir, ct);
_tasks.Add(newTask);
}
while (_tasks.Count > 0)
{
lock (_collectionLock)
{
var t = _tasks.Where(x => x.IsCompleted == true).ToList();
if (t != null)
foreach (var task in t)
{
_tasks.Remove(task);
}
}
}
OutputFiles();
StopAndCleanup();
}
catch (Exception ex)
{
Log(LogColor.Red, "Error: " + ex.Message, false);
_cts.Cancel();
}
}
static Task CreateNewTask(string Path, CancellationToken ct)
{
return Task.Factory.StartNew(() => GetDirectoryFiles(Path, ct), ct);
}
static void GetDirectoryFiles(string Path, CancellationToken ct)
{
if (!ct.IsCancellationRequested)
{
List<string> subDirs = new List<string>();
int currentFileCount = 0;
try
{
currentFileCount = Directory.GetFiles(Path, _fileExtension).Count();
subDirs = Directory.GetDirectories(Path).ToList();
lock (_objLock)
{
_overallFileCount += currentFileCount;
Log(LogColor.White, "- Current path: " + Path);
Log(LogColor.Yellow, "-- Sub directory count: " + subDirs.Count);
Log(LogColor.Yellow, "-- File extension: " + _fileExtension);
Log(LogColor.Yellow, "-- Current count: " + currentFileCount);
Log(LogColor.Red, "-- Running total: " + _overallFileCount);
_csvBuilder.Add(string.Format("{0},{1},{2},{3}", Path, subDirs.Count, _fileExtension, currentFileCount));
Console.Clear();
Log(LogColor.White, "Running file count: " + _overallFileCount, false, true);
}
foreach (var dir in subDirs)
{
lock (_collectionLock)
{
var newTask = CreateNewTask(dir, ct);
_tasks.Add(newTask);
}
}
}
catch (Exception ex)
{
Log(LogColor.Red, "Error: " + ex.Message, false);
_cts.Cancel();
}
}
}
I don't think there's any issue with what you're trying to do, just be cautious about uncontrolled concurrency e.g. reading too many directories at once on different threads. Context switching could end up making it slower.
Instead of doing things as side effects in your methods, try returning the collected values. e.g.
static async Task<IEnumerable<DirectoryStat>> GetDirectoryFiles(string path, string fileExtension, CancellationToken ct)
{
var thisDirectory = await Task.Run(() => /* Get directory file count and return a DirectoryStat object */);
var subDirectoriesResults = await Task.WhenAll(Directory.GetDirectories(path).Select(dir => GetDirectoryFiles(dir, fileExtension, ct)));
return (new[] { thisDirectory }).Concat(subDirectoryResults);
}
You can then iterate them later and pull the data you need from DirectoryStat (and sum your file counts as per _overallFileCount etc)
NOTE: Untested :)
You can run Synchronous Code Async with Task.Run(() => { //code });
Also change your Return Type to Taskso you can await it
I would rewrite you code as follows:
static void RunProgram(CancellationToken ct)
{
try
{
foreach (var dir in _directoriesToProcess)
{
var newTask = CreateNewTask(dir, ct);
_tasks.Add(newTask);
}
//change your while so it does not execute all the time
while (_tasks.Count > 0)
{
lock (_collectionLock)
{
var tsk = _tasks.FirstOrDefault();
if (tsk != null)
{
if (tsk.Status <= TaskStatus.Running)
await tsk;
_tasks.Remove(tsk);
}
}
}
OutputFiles();
StopAndCleanup();
}
catch (Exception ex)
{
Log(LogColor.Red, "Error: " + ex.Message, false);
_cts.Cancel();
}
}
static Task CreateNewTask(string Path, CancellationToken ct)
{
return Task.Factory.StartNew(() => GetDirectoryFiles(Path, ct), ct);
}
//always use Task (or Task<T>) as return so you can await the process
static async Task GetDirectoryFiles(string Path, CancellationToken ct)
{
if (!ct.IsCancellationRequested)
{
//Insert Magic
await Task.Run(() => {
List<string> subDirs = new List<string>();
int currentFileCount = 0;
try
{
currentFileCount = Directory.GetFiles(Path, _fileExtension).Count();
subDirs = Directory.GetDirectories(Path).ToList();
lock (_objLock)
{
_overallFileCount += currentFileCount;
Log(LogColor.White, "- Current path: " + Path);
Log(LogColor.Yellow, "-- Sub directory count: " + subDirs.Count);
Log(LogColor.Yellow, "-- File extension: " + _fileExtension);
Log(LogColor.Yellow, "-- Current count: " + currentFileCount);
Log(LogColor.Red, "-- Running total: " + _overallFileCount);
_csvBuilder.Add(string.Format("{0},{1},{2},{3}", Path, subDirs.Count, _fileExtension, currentFileCount));
Console.Clear();
Log(LogColor.White, "Running file count: " + _overallFileCount, false, true);
}
foreach (var dir in subDirs)
{
lock (_collectionLock)
{
var newTask = CreateNewTask(dir, ct);
_tasks.Add(newTask);
}
}
});
}
catch (Exception ex)
{
Log(LogColor.Red, "Error: " + ex.Message, false);
_cts.Cancel();
}
}
}

Categories

Resources